2026-06-22 21:57:32 +02:00
|
|
|
import { create } from 'zustand'
|
|
|
|
|
import type { PeerInfo, NetworkInfo, ChatMessage, FileEntry, IpcMessage } from '../types'
|
|
|
|
|
import { DaemonAdapter } from '../adapter/daemon'
|
|
|
|
|
|
|
|
|
|
interface WasteState {
|
|
|
|
|
// connection
|
|
|
|
|
adapter: DaemonAdapter | null
|
|
|
|
|
daemonStatus: 'disconnected' | 'connecting' | 'connected'
|
|
|
|
|
|
|
|
|
|
// identity
|
2026-06-22 23:22:32 +02:00
|
|
|
masterAlias: string | null
|
|
|
|
|
masterId: string | null
|
2026-06-22 21:57:32 +02:00
|
|
|
localPeer: PeerInfo | null
|
|
|
|
|
|
|
|
|
|
// networks
|
|
|
|
|
networks: NetworkInfo[]
|
|
|
|
|
activeNetworkId: string | null
|
|
|
|
|
|
|
|
|
|
// peers
|
|
|
|
|
connectedPeers: PeerInfo[]
|
|
|
|
|
|
|
|
|
|
// chat — keyed by room
|
|
|
|
|
messages: Record<string, ChatMessage[]>
|
|
|
|
|
activeRoom: string
|
|
|
|
|
|
|
|
|
|
// file listings — keyed by peer id
|
|
|
|
|
fileLists: Record<string, FileEntry[]>
|
|
|
|
|
|
2026-06-22 23:22:32 +02:00
|
|
|
// identity backup export result (cleared when consumed)
|
|
|
|
|
exportedBackup: string | null
|
|
|
|
|
|
2026-06-22 21:57:32 +02:00
|
|
|
// actions
|
|
|
|
|
connect: (url: string) => void
|
|
|
|
|
disconnect: () => void
|
|
|
|
|
send: (msg: IpcMessage) => void
|
|
|
|
|
setActiveNetwork: (id: string) => void
|
|
|
|
|
setActiveRoom: (room: string) => void
|
|
|
|
|
handleEvent: (msg: IpcMessage) => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const useWaste = create<WasteState>((set, get) => ({
|
|
|
|
|
adapter: null,
|
|
|
|
|
daemonStatus: 'disconnected',
|
2026-06-22 23:22:32 +02:00
|
|
|
masterAlias: null,
|
|
|
|
|
masterId: null,
|
2026-06-22 21:57:32 +02:00
|
|
|
localPeer: null,
|
|
|
|
|
networks: [],
|
|
|
|
|
activeNetworkId: null,
|
|
|
|
|
connectedPeers: [],
|
|
|
|
|
messages: {},
|
|
|
|
|
activeRoom: 'general',
|
|
|
|
|
fileLists: {},
|
2026-06-22 23:22:32 +02:00
|
|
|
exportedBackup: null,
|
2026-06-22 21:57:32 +02:00
|
|
|
|
|
|
|
|
connect(url: string) {
|
|
|
|
|
const adapter = new DaemonAdapter(url)
|
|
|
|
|
adapter.onStatusChange = (s) => set({ daemonStatus: s })
|
|
|
|
|
adapter.on((msg) => get().handleEvent(msg))
|
|
|
|
|
adapter.connect()
|
|
|
|
|
set({ adapter })
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
disconnect() {
|
|
|
|
|
get().adapter?.disconnect()
|
|
|
|
|
set({ adapter: null, daemonStatus: 'disconnected' })
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
send(msg) {
|
|
|
|
|
get().adapter?.send(msg)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setActiveNetwork(id) {
|
|
|
|
|
set({ activeNetworkId: id })
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setActiveRoom(room) {
|
|
|
|
|
set({ activeRoom: room })
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleEvent(msg) {
|
|
|
|
|
switch (msg.type) {
|
|
|
|
|
case 'state_snapshot': {
|
|
|
|
|
const networks = msg.networks ?? []
|
|
|
|
|
set({
|
2026-06-22 23:22:32 +02:00
|
|
|
masterAlias: msg.master_alias ?? null,
|
|
|
|
|
masterId: msg.master_id ?? null,
|
2026-06-22 21:57:32 +02:00
|
|
|
localPeer: msg.local_peer ?? null,
|
|
|
|
|
networks,
|
|
|
|
|
connectedPeers: msg.connected_peers ?? [],
|
|
|
|
|
activeNetworkId: networks[0]?.network_id ?? null,
|
|
|
|
|
})
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case 'network_joined': {
|
|
|
|
|
set(s => ({
|
|
|
|
|
networks: s.networks.some(n => n.network_id === msg.network_id)
|
|
|
|
|
? s.networks
|
|
|
|
|
: [...s.networks, {
|
|
|
|
|
network_id: msg.network_id!,
|
|
|
|
|
network_name: msg.network_name!,
|
|
|
|
|
share_dir: msg.share_dir,
|
|
|
|
|
}],
|
|
|
|
|
activeNetworkId: s.activeNetworkId ?? msg.network_id!,
|
|
|
|
|
}))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case 'network_left': {
|
|
|
|
|
set(s => ({
|
|
|
|
|
networks: s.networks.filter(n => n.network_id !== msg.network_id),
|
|
|
|
|
activeNetworkId: s.activeNetworkId === msg.network_id
|
|
|
|
|
? (s.networks.find(n => n.network_id !== msg.network_id)?.network_id ?? null)
|
|
|
|
|
: s.activeNetworkId,
|
|
|
|
|
}))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case 'peer_connected': {
|
|
|
|
|
if (msg.peer) {
|
|
|
|
|
set(s => ({
|
|
|
|
|
connectedPeers: s.connectedPeers.some(p => p.id === msg.peer!.id)
|
|
|
|
|
? s.connectedPeers
|
|
|
|
|
: [...s.connectedPeers, msg.peer!],
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2026-06-22 23:22:32 +02:00
|
|
|
case 'session_ready': {
|
|
|
|
|
if (msg.peer_id && msg.nick) {
|
|
|
|
|
set(s => ({
|
|
|
|
|
connectedPeers: s.connectedPeers.map(p =>
|
|
|
|
|
p.id === msg.peer_id ? { ...p, alias: msg.nick! } : p
|
|
|
|
|
),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2026-06-22 21:57:32 +02:00
|
|
|
case 'peer_disconnected': {
|
|
|
|
|
set(s => ({
|
|
|
|
|
connectedPeers: s.connectedPeers.filter(p => p.id !== msg.peer_id),
|
|
|
|
|
}))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
case 'message_received': {
|
|
|
|
|
if (msg.message) {
|
2026-06-22 23:22:32 +02:00
|
|
|
const m = msg.message
|
|
|
|
|
const room = m.room
|
|
|
|
|
set(s => {
|
|
|
|
|
const existing = s.messages[room] ?? []
|
|
|
|
|
if (m.mid && existing.some(e => e.mid === m.mid)) return s
|
|
|
|
|
return { messages: { ...s.messages, [room]: [...existing, m] } }
|
|
|
|
|
})
|
2026-06-22 21:57:32 +02:00
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
2026-06-22 23:22:32 +02:00
|
|
|
case 'identity_exported': {
|
|
|
|
|
if (msg.backup) set({ exportedBackup: msg.backup })
|
|
|
|
|
break
|
|
|
|
|
}
|
2026-06-22 21:57:32 +02:00
|
|
|
case 'file_list': {
|
|
|
|
|
if (msg.peer_id && msg.files) {
|
|
|
|
|
set(s => ({
|
|
|
|
|
fileLists: { ...s.fileLists, [msg.peer_id!]: msg.files! },
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}))
|