import { create } from 'zustand' import type { PeerInfo, NetworkInfo, ChatMessage, FileEntry, IpcMessage, PeerConnState, CandidateType } from '../types' export interface PeerStatus { connState?: PeerConnState candidateType?: CandidateType remoteAddress?: string lastSeen?: number } import { DaemonAdapter } from '../adapter/daemon' import { BrowserAdapter } from '../adapter/browser' type AnyAdapter = DaemonAdapter | BrowserAdapter interface WasteState { // connection adapter: AnyAdapter | null adapterMode: 'daemon' | 'browser' | null daemonStatus: 'disconnected' | 'connecting' | 'connected' // identity masterAlias: string | null masterId: string | null localPeer: PeerInfo | null // networks networks: NetworkInfo[] activeNetworkId: string | null // peers connectedPeers: PeerInfo[] // chat — keyed by room messages: Record activeRoom: string // file listings — keyed by peer id fileLists: Record // per-peer connection status (browser mode) and last-seen timestamps peerStatus: Record // identity backup export result (cleared when consumed) exportedBackup: string | null // actions connect: (url: string) => void connectBrowser: () => void disconnect: () => void send: (msg: IpcMessage) => void setActiveNetwork: (id: string) => void setActiveRoom: (room: string) => void handleEvent: (msg: IpcMessage) => void } export const useWaste = create((set, get) => ({ adapter: null, adapterMode: null, daemonStatus: 'disconnected', masterAlias: null, masterId: null, localPeer: null, networks: [], activeNetworkId: null, connectedPeers: [], messages: {}, activeRoom: 'general', fileLists: {}, exportedBackup: null, peerStatus: {}, connect(url: string) { const adapter = new DaemonAdapter(url) adapter.onStatusChange = (s) => set({ daemonStatus: s }) adapter.on((msg) => get().handleEvent(msg)) adapter.connect() set({ adapter, adapterMode: 'daemon' }) }, connectBrowser() { const adapter = new BrowserAdapter() adapter.onStatusChange = (s) => set({ daemonStatus: s }) adapter.on((msg) => get().handleEvent(msg)) adapter.connect() set({ adapter, adapterMode: 'browser' }) }, disconnect() { const a = get().adapter if (a instanceof DaemonAdapter) a.disconnect() else if (a instanceof BrowserAdapter) a.disconnect() set({ adapter: null, adapterMode: 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({ masterAlias: msg.master_alias ?? null, masterId: msg.master_id ?? null, localPeer: msg.local_peer ?? null, networks, connectedPeers: msg.connected_peers ?? [], activeNetworkId: networks[0]?.network_id ?? null, }) break } case 'network_joined': { set(s => ({ localPeer: s.localPeer ?? msg.local_peer ?? null, 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!, local_peer: msg.local_peer, 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 } 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 } case 'peer_disconnected': { set(s => ({ connectedPeers: s.connectedPeers.filter(p => p.id !== msg.peer_id), })) break } case 'message_received': { if (msg.message) { const m = msg.message const room = m.room const fromId = String(m.from) set(s => { const existing = s.messages[room] ?? [] if (m.mid && existing.some(e => e.mid === m.mid)) return s const prev = s.peerStatus[fromId] ?? {} return { messages: { ...s.messages, [room]: [...existing, m] }, peerStatus: { ...s.peerStatus, [fromId]: { ...prev, lastSeen: m.ts } }, } }) } break } case 'peer_status': { const pid = String(msg.peer_id) set(s => { const prev = s.peerStatus[pid] ?? {} return { peerStatus: { ...s.peerStatus, [pid]: { ...prev, ...(msg.conn_state ? { connState: msg.conn_state } : {}), ...(msg.candidate_type ? { candidateType: msg.candidate_type } : {}), ...(msg.remote_address !== undefined ? { remoteAddress: msg.remote_address } : {}), }, }, } }) break } case 'identity_exported': { if (msg.backup) set({ exportedBackup: msg.backup }) break } case 'file_list': { if (msg.peer_id && msg.files) { set(s => ({ fileLists: { ...s.fileLists, [msg.peer_id!]: msg.files! }, })) } break } } }, }))