13 KiB
Intégration Gotify - Notifications Push
Ce document décrit l'intégration de Gotify pour les notifications push dans Mesh.
📋 Vue d'Ensemble
Gotify est utilisé pour envoyer des notifications push aux utilisateurs lorsqu'ils sont absents (non connectés via WebSocket). Les notifications sont envoyées pour:
- Messages de chat - Quand un utilisateur reçoit un message alors qu'il n'est pas dans la room
- Appels WebRTC - Quand quelqu'un essaie d'appeler un utilisateur absent
- Partages de fichiers (future) - Quand un fichier est partagé avec un utilisateur absent
Principe clé: Les notifications sont envoyées uniquement si l'utilisateur est absent. Si l'utilisateur est connecté et actif dans la room, il reçoit les événements via WebSocket en temps réel (pas de notification).
⚙️ Configuration
Serveur Gotify
URL de test: http://10.0.0.5:8185
Application: mesh
Token: AvKcy9o-yvVhyKd
Variables d'Environnement
Dans server/.env:
# Gotify Integration
GOTIFY_URL=http://10.0.0.5:8185
GOTIFY_TOKEN=AvKcy9o-yvVhyKd
Notes:
GOTIFY_URLetGOTIFY_TOKENsont optionnels- Si non configurés, les notifications sont désactivées (logs warning)
- Le serveur Mesh fonctionne normalement sans Gotify
Configuration dans le Code
Fichier: server/src/config.py
# Gotify (optionnel)
gotify_url: Optional[str] = None
gotify_token: Optional[str] = None
🏗️ Architecture
Client Gotify
Fichier: server/src/notifications/gotify.py
Classe principale: GotifyClient
class GotifyClient:
def __init__(self):
self.url = settings.GOTIFY_URL
self.token = settings.GOTIFY_TOKEN
self.enabled = bool(self.url and self.token)
async def send_notification(
title: str,
message: str,
priority: int = 5,
extras: Optional[Dict[str, Any]] = None
) -> bool
Méthodes spécifiques:
send_chat_notification()- Notification de chatsend_call_notification()- Notification d'appel WebRTCsend_file_notification()- Notification de fichier (future)
Instance Globale
from src.notifications.gotify import gotify_client
# Utilisation
await gotify_client.send_chat_notification(
from_username="Alice",
room_name="Team Chat",
message="Hello Bob!",
room_id="room-uuid"
)
📨 Types de Notifications
1. Messages de Chat
Trigger: Utilisateur envoie un message via WebSocket
Condition: Destinataire pas connecté dans la room
Exemple:
{
"title": "💬 Alice dans Team Chat",
"message": "Hey, can you review my PR?",
"priority": 6,
"extras": {
"client::notification": {
"click": {
"url": "mesh://room/abc-123-def"
}
}
}
}
Code (server/src/websocket/handlers.py):
async def handle_chat_message_send(...):
# ... créer et broadcast message ...
# Envoyer notifications aux absents
await self._send_chat_notifications(
room, sender, content, room_id_str, peer_id
)
Logique:
async def _send_chat_notifications(...):
members = db.query(RoomMember).filter(...)
for member in members:
if member.user_id == sender.id:
continue # Pas de notif pour l'expéditeur
is_online = manager.is_user_in_room(user.user_id, room_id)
if not is_online:
await gotify_client.send_chat_notification(...)
2. Appels WebRTC
Trigger: Utilisateur envoie un rtc.offer via WebSocket
Condition: Destinataire pas connecté
Exemple:
{
"title": "📞 Appel audio/vidéo de Alice",
"message": "Appel entrant dans Team Chat",
"priority": 8,
"extras": {
"client::notification": {
"click": {
"url": "mesh://room/abc-123-def"
}
}
}
}
Code (server/src/websocket/handlers.py):
async def handle_rtc_signal(...):
if event_data.get("type") == EventType.RTC_OFFER:
target_is_online = manager.is_connected(target_peer_id)
if not target_is_online:
await gotify_client.send_call_notification(
from_username=user.username,
room_name=room.name,
room_id=room_id,
call_type="audio/vidéo"
)
3. Partages de Fichiers (Future)
Trigger: Utilisateur partage un fichier via P2P
Condition: Destinataire pas connecté
Exemple:
{
"title": "📁 Alice a partagé un fichier",
"message": "Fichier: document.pdf\nDans: Team Chat",
"priority": 5,
"extras": {
"client::notification": {
"click": {
"url": "mesh://room/abc-123-def"
}
}
}
}
Code (à implémenter):
await gotify_client.send_file_notification(
from_username="Alice",
room_name="Team Chat",
filename="document.pdf",
room_id="abc-123"
)
🔔 Niveaux de Priorité
Gotify utilise des priorités de 0 (minimum) à 10 (maximum).
| Type | Priorité | Raison |
|---|---|---|
| Messages de chat | 6 | Important mais pas urgent |
| Appels WebRTC | 8 | Haute priorité (appel entrant) |
| Fichiers partagés | 5 | Normal |
| Erreurs système | 7 | Attention requise |
Mapping Gotify:
- 0-3: Silent / Low
- 4-7: Normal
- 8-10: High / Emergency
🎯 Extras et Actions
Gotify supporte des métadonnées supplémentaires pour enrichir les notifications.
Click Action
"extras": {
"client::notification": {
"click": {
"url": "mesh://room/{room_id}"
}
}
}
Comportement:
- Clic sur notification → Ouvre l'app Mesh sur la room
- URL scheme:
mesh://room/{room_id}
Android Actions
"extras": {
"android::action": {
"onReceive": {
"intentUrl": "mesh://room/{room_id}"
}
}
}
Comportement:
- Android intent pour deep linking
- Compatible avec apps mobiles
Markdown Content
"extras": {
"client::display": {
"contentType": "text/markdown"
}
}
Comportement:
- Message formaté en Markdown
- Liens, bold, italique supportés
🧪 Tests
Test 1: Envoi Direct
Fichier: server/test_gotify.py
cd server
python3 test_gotify.py
Résultat attendu:
✅ Notification envoyée avec succès à Gotify
Response: {'id': 78623, 'appid': 4, ...}
Vérification:
- Ouvrir l'app Gotify sur mobile/web
- Notification visible avec titre "🧪 Test Mesh"
Test 2: Chat End-to-End
Setup:
- Alice et Bob créent des comptes
- Alice crée une room "Test Gotify"
- Alice invite Bob à la room
- Bob se déconnecte (ferme navigateur)
- Alice envoie un message dans la room
Résultat attendu:
- Bob reçoit une notification Gotify sur son téléphone
- Titre: "💬 Alice dans Test Gotify"
- Message: Contenu du message d'Alice (tronqué à 100 chars)
- Clic → Ouvre Mesh sur la room
Logs serveur:
INFO - Notification Gotify envoyée à bob pour message dans Test Gotify
Test 3: Appel WebRTC
Setup:
- Alice et Bob dans la room "Test Gotify"
- Bob se déconnecte
- Alice active sa caméra (déclenche WebRTC offer)
Résultat attendu:
- Bob reçoit une notification Gotify
- Titre: "📞 Appel audio/vidéo de Alice"
- Message: "Appel entrant dans Test Gotify"
- Priorité: 8 (haute)
Logs serveur:
DEBUG - Relayed rtc.offer from peer_xxx to peer_yyy
🔍 Debugging
Vérifier Configuration
# Dans server/src/notifications/gotify.py
logger.info(f"Gotify configuré: {self.url}")
logger.info(f"Gotify enabled: {self.enabled}")
Logs attendus:
INFO - Gotify configuré: http://10.0.0.5:8185
INFO - Gotify enabled: True
Si enabled: False:
WARNING - Gotify non configuré - notifications désactivées
Tester Envoi HTTP
curl -X POST "http://10.0.0.5:8185/message?token=AvKcy9o-yvVhyKd" \
-H "Content-Type: application/json" \
-d '{
"title": "Test cURL",
"message": "Hello from cURL",
"priority": 5
}'
Réponse attendue:
{
"id": 78624,
"appid": 4,
"message": "Hello from cURL",
"title": "Test cURL",
"priority": 5,
"date": "2026-01-04T08:00:00Z"
}
Logs Détaillés
Activer DEBUG dans server/.env:
LOG_LEVEL=DEBUG
Relancer serveur:
docker restart mesh-server
docker logs -f mesh-server
Logs attendus:
DEBUG - Notification Gotify envoyée à bob pour message dans Team Chat
INFO - Notification Gotify envoyée: 💬 Alice dans Team Chat
🚨 Gestion des Erreurs
Gotify Inaccessible
try:
response = await client.post(...)
except httpx.HTTPError as e:
logger.error(f"Erreur envoi Gotify: {e}")
return False
Comportement:
- Erreur loggée
- Notification non envoyée
- Application continue normalement
- WebSocket events toujours envoyés
Token Invalide
Erreur HTTP: 401 Unauthorized
Log:
ERROR - Erreur envoi Gotify: 401 Client Error: Unauthorized
Fix:
- Vérifier
GOTIFY_TOKENdans.env - Régénérer token dans Gotify si nécessaire
Timeout
Config:
async with httpx.AsyncClient(timeout=5.0) as client:
Erreur: httpx.ReadTimeout
Log:
ERROR - Erreur envoi Gotify: ReadTimeout
Fix:
- Vérifier connectivité réseau
- Augmenter timeout si nécessaire
📊 Métriques
Taux d'Envoi
Avec 100 utilisateurs et 10 messages/minute:
- Utilisateurs en ligne: ~70%
- Utilisateurs absents: ~30%
- Notifications Gotify: ~30/minute (seulement les absents)
Performance
Latence envoi: <100ms (réseau local)
Timeout: 5s (configurable)
Impact serveur: Négligeable (requêtes async)
🔐 Sécurité
Token Gotify
Stockage: Variable d'environnement .env
Permissions: Le token doit avoir permission messages:create
Rotation: Régénérer le token régulièrement en production
URL Scheme
Format: mesh://room/{room_id}
Validation: Le client mobile doit valider le room_id
Sécurité: Pas de données sensibles dans l'URL
Contenu Messages
Tronqué: Messages >100 chars sont tronqués
Sanitization: Pas d'exécution de code dans les messages
Markdown: Désactivé par défaut (text/plain)
🚀 Production
Variables d'Environnement
# Production
GOTIFY_URL=https://gotify.yourdomain.com
GOTIFY_TOKEN=your-production-token-change-this
HTTPS
⚠️ Obligatoire en production
GOTIFY_URL=https://gotify.yourdomain.com
Pas de HTTP en production pour éviter:
- Interception du token
- Man-in-the-middle attacks
High Availability
Option 1: Gotify derrière load balancer
Option 2: Queue de notifications (Redis)
- Si Gotify down → Queue les notifications
- Retry automatique
- Pas de perte de notifications
Option 3: Fallback multiple providers
- Gotify primaire
- FCM/APNS fallback
- Email en dernier recours
📱 Client Mobile (Future)
Deep Linking
iOS:
// AppDelegate.swift
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
if url.scheme == "mesh" {
// Parse: mesh://room/{room_id}
let roomId = url.host
navigateToRoom(roomId)
}
}
Android:
<!-- AndroidManifest.xml -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="mesh" android:host="room" />
</intent-filter>
Gotify Client
iOS/Android: Utiliser l'app Gotify officielle
Custom app: Implémenter WebSocket Gotify
wss://gotify.yourdomain.com/stream?token=xxx- Recevoir notifications en temps réel
🔗 Références
✅ Checklist Déploiement
Avant de déployer en production:
- Gotify serveur installé et accessible
- HTTPS activé sur Gotify
- Token Gotify créé avec permissions correctes
- Variables
GOTIFY_URLetGOTIFY_TOKENdans.env - Test envoi direct réussi (
test_gotify.py) - Test end-to-end chat réussi
- Test end-to-end appel WebRTC réussi
- Logs serveur confirmant envois
- App mobile configurée avec deep linking
- Monitoring des erreurs Gotify (logs)
- Plan de fallback si Gotify down
Intégration complète et testée! 🎉