Websocket for contact deletion, radio contact deletion flag fix, resent message now appends sender name

This commit is contained in:
Jack Kingsman
2026-03-03 12:43:27 -08:00
parent 73d4647cfc
commit 8fa37fe6dc
6 changed files with 45 additions and 0 deletions

View File

@@ -215,6 +215,19 @@ class ContactRepository:
)
await db.conn.commit()
@staticmethod
async def clear_on_radio_except(keep_keys: list[str]) -> None:
"""Set on_radio=False for all contacts NOT in keep_keys."""
if not keep_keys:
await db.conn.execute("UPDATE contacts SET on_radio = 0 WHERE on_radio = 1")
else:
placeholders = ",".join("?" * len(keep_keys))
await db.conn.execute(
f"UPDATE contacts SET on_radio = 0 WHERE on_radio = 1 AND public_key NOT IN ({placeholders})",
keep_keys,
)
await db.conn.commit()
@staticmethod
async def delete(public_key: str) -> None:
normalized = public_key.lower()

View File

@@ -127,4 +127,9 @@ async def delete_channel(key: str) -> dict:
"""
logger.info("Deleting channel %s from database", key)
await ChannelRepository.delete(key)
from app.websocket import broadcast_event
broadcast_event("channel_deleted", {"key": key})
return {"status": "ok"}

View File

@@ -271,16 +271,21 @@ async def sync_contacts_from_radio() -> dict:
contacts = result.payload
count = 0
synced_keys: list[str] = []
for public_key, contact_data in contacts.items():
lower_key = public_key.lower()
await ContactRepository.upsert(
Contact.from_radio_dict(lower_key, contact_data, on_radio=True)
)
synced_keys.append(lower_key)
claimed = await MessageRepository.claim_prefix_messages(lower_key)
if claimed > 0:
logger.info("Claimed %d prefix DM message(s) for contact %s", claimed, public_key[:12])
count += 1
# Clear on_radio for contacts not found on the radio
await ContactRepository.clear_on_radio_except(synced_keys)
logger.info("Synced %d contacts from radio", count)
return {"synced": count}
@@ -368,6 +373,10 @@ async def delete_contact(public_key: str) -> dict:
await ContactRepository.delete(contact.public_key)
logger.info("Deleted contact %s", contact.public_key[:12])
from app.websocket import broadcast_event
broadcast_event("contact_deleted", {"public_key": contact.public_key})
return {"status": "ok"}

View File

@@ -399,6 +399,7 @@ async def resend_channel_message(
sender_timestamp=now,
received_at=now,
outgoing=True,
sender_name=radio_name or None,
)
if new_msg_id is None:
# Timestamp-second collision (same text+channel within the same second).

View File

@@ -287,6 +287,14 @@ export function App() {
onContact: (contact: Contact) => {
setContacts((prev) => mergeContactIntoList(prev, contact));
},
onContactDeleted: (publicKey: string) => {
setContacts((prev) => prev.filter((c) => c.public_key !== publicKey));
messageCache.remove(publicKey);
},
onChannelDeleted: (key: string) => {
setChannels((prev) => prev.filter((c) => c.key !== key));
messageCache.remove(key);
},
onRawPacket: (packet: RawPacket) => {
setRawPackets((prev) => appendRawPacketUnique(prev, packet, MAX_RAW_PACKETS));
},
@@ -306,6 +314,7 @@ export function App() {
setConfig,
activeConversationRef,
setContacts,
setChannels,
triggerReconcile,
refreshUnreads,
fetchAllContacts,

View File

@@ -20,6 +20,8 @@ interface UseWebSocketOptions {
onHealth?: (health: HealthStatus) => void;
onMessage?: (message: Message) => void;
onContact?: (contact: Contact) => void;
onContactDeleted?: (publicKey: string) => void;
onChannelDeleted?: (key: string) => void;
onRawPacket?: (packet: RawPacket) => void;
onMessageAcked?: (messageId: number, ackCount: number, paths?: MessagePath[]) => void;
onError?: (error: ErrorEvent) => void;
@@ -103,6 +105,12 @@ export function useWebSocket(options: UseWebSocketOptions) {
case 'contact':
handlers.onContact?.(msg.data as Contact);
break;
case 'contact_deleted':
handlers.onContactDeleted?.((msg.data as { public_key: string }).public_key);
break;
case 'channel_deleted':
handlers.onChannelDeleted?.((msg.data as { key: string }).key);
break;
case 'raw_packet':
handlers.onRawPacket?.(msg.data as RawPacket);
break;