From ef98efb93883d8c71719a063f790945312a2d28d Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 17:59:57 +0200 Subject: [PATCH] feat(desktop): mention notifications via realtime + osNotify --- apps/desktop/src/components/AppShell.tsx | 2 + .../src/lib/useMentionNotifications.ts | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 apps/desktop/src/lib/useMentionNotifications.ts diff --git a/apps/desktop/src/components/AppShell.tsx b/apps/desktop/src/components/AppShell.tsx index a438a61..47d2fd0 100644 --- a/apps/desktop/src/components/AppShell.tsx +++ b/apps/desktop/src/components/AppShell.tsx @@ -6,6 +6,7 @@ import { startConversationKeySync } from '../lib/conversationKeySync'; import { startDeviceApprovalListener } from '../lib/deviceApproval'; import { ensureInstallId } from '../lib/installId'; import { ensureNotificationPermission } from '../lib/osNotify'; +import { useMentionNotifications } from '../lib/useMentionNotifications'; import { cachedUserKey } from '../lib/userIdentity'; import { CallUI } from './CallUI'; import { DeviceApprovalBanner } from './DeviceApprovalBanner'; @@ -13,6 +14,7 @@ import { Sidebar } from './Sidebar'; export function AppShell() { const { session } = useAuth(); + useMentionNotifications(session?.user.id); useEffect(() => { // Prompt once per authenticated shell mount. Module-level guard prevents // re-asking if the user already responded this session. diff --git a/apps/desktop/src/lib/useMentionNotifications.ts b/apps/desktop/src/lib/useMentionNotifications.ts new file mode 100644 index 0000000..3382f18 --- /dev/null +++ b/apps/desktop/src/lib/useMentionNotifications.ts @@ -0,0 +1,45 @@ +import { useEffect } from 'react'; + +import { notify } from './osNotify'; +import { supabase } from './supabase'; + +interface MentionRow { + message_id: string; + mentioned_user_id: string; + conversation_id: string; +} + +// Subscribes to my own message_mentions inserts and fires an OS notification +// for each one. Bypasses per-conv mute (mentions override mute by design). +// +// We don't decrypt the body here — the notification just says "Du wurdest +// erwähnt". The conv list highlight + the in-app navigation reveal context. +export function useMentionNotifications(userId: string | undefined): void { + useEffect(() => { + if (!userId) return; + const channel = supabase + .channel('mentions:' + userId) + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'message_mentions', + filter: 'mentioned_user_id=eq.' + userId, + }, + (payload) => { + const row = payload.new as MentionRow | null; + if (!row) return; + void notify({ + title: 'Du wurdest erwähnt', + body: 'Tippe um die Nachricht zu lesen.', + force: true, + }); + }, + ) + .subscribe(); + return () => { + void supabase.removeChannel(channel); + }; + }, [userId]); +}