From eed4f293bcc4f1aaf62b2224f1471527499a9aa6 Mon Sep 17 00:00:00 2001 From: Dicken Date: Sat, 30 May 2026 01:33:15 +0200 Subject: [PATCH] =?UTF-8?q?Nachrichten:=20kein=20Pushover=20wenn=20Empf?= =?UTF-8?q?=C3=A4nger=20aktiv=20im=20Chat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/db.js | 2 ++ backend/src/tools/nachrichten/routes.js | 21 +++++++++++++-- frontend/src/tools/nachrichten.jsx | 34 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/backend/src/db.js b/backend/src/db.js index 032ebf2..a288df7 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -254,6 +254,8 @@ if (!userCols2.includes('last_active_at')) db.exec("ALTER TABLE users ADD COLUMN last_active_at DATETIME DEFAULT NULL"); if (!userCols2.includes('hidden')) db.exec("ALTER TABLE users ADD COLUMN hidden INTEGER NOT NULL DEFAULT 0"); +if (!userCols2.includes('chat_active_at')) + db.exec("ALTER TABLE users ADD COLUMN chat_active_at DATETIME DEFAULT NULL"); // ── Login-Sicherheit ────────────────────────────────────────────────────────── // Spalten für Account-Lockout in users-Tabelle diff --git a/backend/src/tools/nachrichten/routes.js b/backend/src/tools/nachrichten/routes.js index 1c1e58c..3ee9f1f 100644 --- a/backend/src/tools/nachrichten/routes.js +++ b/backend/src/tools/nachrichten/routes.js @@ -38,6 +38,17 @@ router.post('/keys', authenticate, (req, res) => { }); // ── Users ───────────────────────────────────────────────────────────────────── +// Presence: User ist gerade aktiv im Chat +router.post('/presence', authenticate, (req, res) => { + const { active } = req.body; + if (active) { + db.prepare("UPDATE users SET chat_active_at=datetime('now','localtime') WHERE id=?").run(req.user.id); + } else { + db.prepare("UPDATE users SET chat_active_at=NULL WHERE id=?").run(req.user.id); + } + res.json({ ok: true }); +}); + router.get('/users', authenticate, (req, res) => { const me = req.user.id; const isAdmin = req.user.role === 'admin'; @@ -120,8 +131,14 @@ router.post('/messages/:userId', authenticate, async (req, res) => { const senderName = db.prepare('SELECT username FROM users WHERE id = ?').get(me)?.username || 'Jemand'; const pushover = db.prepare('SELECT * FROM pushover_settings WHERE user_id = ?').get(recipId); if (pushover) { - await sendPushover(pushover.user_key, pushover.app_token, 'DickenDock', `Neue Nachricht von ${senderName}`, - { retry: pushover.retry, expire: pushover.expire }); + // Kein Push wenn Empfänger gerade im Chat ist (aktiv in letzten 45 Sekunden) + const presence = db.prepare(` + SELECT chat_active_at FROM users WHERE id=? AND chat_active_at > datetime('now','localtime','-45 seconds') + `).get(recipId); + if (!presence) { + await sendPushover(pushover.user_key, pushover.app_token, 'DickenDock', `Neue Nachricht von ${senderName}`, + { retry: pushover.retry, expire: pushover.expire }); + } } res.json({ id: result.lastInsertRowid }); diff --git a/frontend/src/tools/nachrichten.jsx b/frontend/src/tools/nachrichten.jsx index 6fa59a9..ca3c477 100644 --- a/frontend/src/tools/nachrichten.jsx +++ b/frontend/src/tools/nachrichten.jsx @@ -95,6 +95,40 @@ export default function Nachrichten({ toast, mobile }) { const bottomRef = useRef(null); const pollRef = useRef(null); const pickerRef = useRef(null); + const heartbeatRef = useRef(null); + + // Presence Heartbeat: solange Nachrichten offen + Tab sichtbar → aktiv + useEffect(() => { + const sendPresence = (active) => api('/tools/nachrichten/presence', { method:'POST', body:{ active } }).catch(()=>{}); + + const start = () => { + if (document.visibilityState === 'visible') { + sendPresence(true); + heartbeatRef.current = setInterval(() => { + if (document.visibilityState === 'visible') sendPresence(true); + }, 30000); + } + }; + + const onVisibility = () => { + if (document.visibilityState === 'visible') { + sendPresence(true); + if (!heartbeatRef.current) heartbeatRef.current = setInterval(() => sendPresence(true), 30000); + } else { + sendPresence(false); + clearInterval(heartbeatRef.current); + heartbeatRef.current = null; + } + }; + + start(); + document.addEventListener('visibilitychange', onVisibility); + return () => { + sendPresence(false); + clearInterval(heartbeatRef.current); + document.removeEventListener('visibilitychange', onVisibility); + }; + }, []); useEffect(() => { if (!showPicker) return;