backup commit
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
// Pinia store for mesh networking state (Meshcore LoRa)
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
export interface MeshStatus {
|
||||
enabled: boolean
|
||||
device_type: string
|
||||
device_path: string | null
|
||||
device_connected: boolean
|
||||
firmware_version: string | null
|
||||
self_node_id: number | null
|
||||
self_advert_name: string | null
|
||||
peer_count: number
|
||||
channel_name: string
|
||||
messages_sent: number
|
||||
messages_received: number
|
||||
detected_devices?: string[]
|
||||
}
|
||||
|
||||
export interface MeshPeer {
|
||||
contact_id: number
|
||||
advert_name: string
|
||||
did: string | null
|
||||
pubkey_hex: string | null
|
||||
rssi: number | null
|
||||
snr: number | null
|
||||
last_heard: string
|
||||
hops: number
|
||||
}
|
||||
|
||||
export interface MeshChannel {
|
||||
index: number
|
||||
name: string
|
||||
has_secret: boolean
|
||||
}
|
||||
|
||||
export interface MeshMessage {
|
||||
id: number
|
||||
direction: 'sent' | 'received'
|
||||
peer_contact_id: number
|
||||
peer_name: string | null
|
||||
plaintext: string
|
||||
timestamp: string
|
||||
delivered: boolean
|
||||
encrypted: boolean
|
||||
}
|
||||
|
||||
export const useMeshStore = defineStore('mesh', () => {
|
||||
const status = ref<MeshStatus | null>(null)
|
||||
const peers = ref<MeshPeer[]>([])
|
||||
const messages = ref<MeshMessage[]>([])
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
const sending = ref(false)
|
||||
|
||||
// Track unread message counts per peer (contact_id -> count)
|
||||
const unreadCounts = ref<Record<number, number>>({})
|
||||
// Currently viewing chat for this contact_id (clears unread)
|
||||
const viewingChatId = ref<number | null>(null)
|
||||
// Total unread count for nav badge
|
||||
const totalUnread = computed(() =>
|
||||
Object.values(unreadCounts.value).reduce((a, b) => a + b, 0)
|
||||
)
|
||||
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const res = await rpcClient.call<MeshStatus>({ method: 'mesh.status' })
|
||||
status.value = res
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to fetch mesh status'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPeers() {
|
||||
try {
|
||||
const res = await rpcClient.call<{ peers: MeshPeer[]; count: number }>({
|
||||
method: 'mesh.peers',
|
||||
})
|
||||
peers.value = res.peers
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to fetch mesh peers'
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMessages(limit?: number) {
|
||||
try {
|
||||
const res = await rpcClient.call<{ messages: MeshMessage[]; count: number }>({
|
||||
method: 'mesh.messages',
|
||||
params: limit ? { limit } : {},
|
||||
})
|
||||
// Detect new incoming messages and increment unread counts
|
||||
const newMsgs = res.messages.filter(
|
||||
m => m.direction === 'received' && !messages.value.some(existing => existing.id === m.id)
|
||||
)
|
||||
for (const msg of newMsgs) {
|
||||
// Don't count as unread if we're currently viewing that chat
|
||||
if (msg.peer_contact_id !== viewingChatId.value) {
|
||||
unreadCounts.value[msg.peer_contact_id] = (unreadCounts.value[msg.peer_contact_id] || 0) + 1
|
||||
}
|
||||
}
|
||||
messages.value = res.messages
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to fetch mesh messages'
|
||||
}
|
||||
}
|
||||
|
||||
function markChatRead(contactId: number) {
|
||||
viewingChatId.value = contactId
|
||||
delete unreadCounts.value[contactId]
|
||||
}
|
||||
|
||||
function clearViewingChat() {
|
||||
viewingChatId.value = null
|
||||
}
|
||||
|
||||
async function sendMessage(contactId: number, message: string) {
|
||||
try {
|
||||
sending.value = true
|
||||
error.value = null
|
||||
const res = await rpcClient.call<{ sent: boolean; message_id: number; encrypted: boolean }>({
|
||||
method: 'mesh.send',
|
||||
params: { contact_id: contactId, message: message.trim() },
|
||||
})
|
||||
// Refresh messages after sending
|
||||
if (res.sent) {
|
||||
await fetchMessages()
|
||||
}
|
||||
return res
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to send mesh message'
|
||||
throw err
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function broadcastIdentity() {
|
||||
try {
|
||||
error.value = null
|
||||
await rpcClient.call<{ broadcast: boolean }>({ method: 'mesh.broadcast' })
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to broadcast identity'
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function configure(config: Partial<MeshStatus>) {
|
||||
try {
|
||||
error.value = null
|
||||
await rpcClient.call<{ configured: boolean }>({
|
||||
method: 'mesh.configure',
|
||||
params: config,
|
||||
})
|
||||
await fetchStatus()
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : 'Failed to configure mesh'
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAll() {
|
||||
await Promise.all([fetchStatus(), fetchPeers(), fetchMessages()])
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
peers,
|
||||
messages,
|
||||
loading,
|
||||
error,
|
||||
sending,
|
||||
unreadCounts,
|
||||
totalUnread,
|
||||
fetchStatus,
|
||||
fetchPeers,
|
||||
fetchMessages,
|
||||
sendMessage,
|
||||
broadcastIdentity,
|
||||
configure,
|
||||
refreshAll,
|
||||
markChatRead,
|
||||
clearViewingChat,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user