Temps réel et notifications
L'application mobile reçoit les événements Socket.IO du backend sur deux écrans, la salle d'attente et le tirage en direct. Elle n'a pas de notifications push : la dépendance expo-notifications est installée mais jamais importée, aucun jeton d'appareil n'est enregistré et le backend n'envoie aucune notification vers Expo, Apple ou Google. L'écran « Notifications » affiche une liste lue par l'API.
Cette page sépare ce qui fonctionne de ce qui n'est pas branché. La liste des événements du serveur est dans Temps réel Socket.IO ; le comportement équivalent du web est dans Temps réel du web.
État des fonctionnalités
| Fonctionnalité | État | Détail |
|---|---|---|
| Connexion Socket.IO authentifiée | branchée | Jeton lu dans SecureStore et passé dans auth.token |
| Salle d'attente du tirage | branchée | draw:join, draw:leave, draw:waiting-room ; la salle est rejointe à nouveau après une reconnexion |
| Discussion en direct | branchée | chat:send, chat:message ; messages dédoublonnés par identifiant |
| Annonce du gagnant | branchée | draw:winner, invalidation de Lottery et Wallet |
| Points attribués en direct | branchée, avec défaut | points:awarded, filtré par userId dans l'application |
| Réactions rapides du serveur | non utilisée | sendReaction émet chat:react, mais aucun écran ne l'appelle ; chat:reaction n'est pas écouté |
| Compte à rebours serveur, début du tirage, file d'attente, bascule de nœud | non émis | draw:countdown, draw:started, queue:position et network:fallback sont déclarés côté serveur mais jamais envoyés ; les écrans interrogent GET /lottery/status à intervalle régulier |
| Notifications push natives | absente | expo-notifications non importé, pas de plugin dans app.json, pas de demande d'autorisation, pas de jeton |
| Liste des notifications | branchée sur des données fixes | GET /user/notifications renvoie trois notifications codées en dur dans le backend |
| Bandeau « notification » | sans déclencheur | triggerSimulatedOsPush existe toujours dans le hook et RootNavigator sait afficher le bandeau, mais plus aucun écran ne l'appelle |
Préférence pushNotificationsEnabled |
enregistrée, sans effet | Interrupteur « Notifications push » de l'écran Paramètres, envoyé par PUT /user/preferences ; rien ne la lit |
Le client Socket.IO
mobile-app/src/services/socket.client.ts expose un singleton :
this.socket = io(WS_BASE_URL, {
transports: ['websocket', 'polling'],
autoConnect: true,
auth: {
token: token || undefined,
},
});| Méthode | Comportement |
|---|---|
connect() |
Asynchrone. Renvoie la connexion existante si elle est connectée ; sinon lit le jeton dans SecureStore et crée une nouvelle connexion |
getSocket() |
Connexion courante ou null |
disconnect() |
Ferme et oublie la connexion ; n'est appelé par aucun écran ni par la déconnexion |
WS_BASE_URL vient de EXPO_PUBLIC_WS_URL (http://localhost:2053 par défaut). Sur un téléphone, il faut l'adresse IP de la machine du backend (voir Tester sur un téléphone). Le nombre de tentatives de reconnexion n'est pas limité : c'est la valeur par défaut de socket.io-client.
Si une connexion existe mais n'est pas encore connectée (ou a été coupée), connect() en crée une seconde sans fermer la première. Le jeton n'est relu qu'à la création : après une reconnexion du compte, l'ancienne connexion garde l'ancien jeton.
Le serveur accepte une connexion sans jeton en invité et refuse un jeton invalide ou révoqué (socket.middleware.ts).
Le hook useSocket
mobile-app/src/hooks/useSocket.ts est utilisé par deux écrans, qui ne lui passent l'identifiant du tour que lorsqu'il est utile :
| Écran | Identifiant passé |
|---|---|
WaitingRoomScreen |
Seulement en phase WAITING_ROOM et si le citoyen a un ticket pour ce tour |
LiveDrawScreen |
Seulement en phase DRAWING, COMPLETED ou CANCELLED |
À chaque exécution de l'effet, le hook :
- attend
socketClient.connect(); - émet
draw:joinavec l'identifiant du tour si la connexion est active, et le réémet à chaque événementconnect, donc après une reconnexion ; - ajoute quatre écouteurs :
draw:waiting-room,chat:message,draw:winner,points:awarded.
La fonction de nettoyage retire ces écouteurs et celui de connect (socket.off), puis émet draw:leave. Les messages ne sont donc plus reçus en double quand l'effet se relance, et addLiveChatMessage ignore de toute façon un identifiant déjà présent.
| Événement reçu | Effet |
|---|---|
draw:waiting-room |
setWaitingRoomOnlineCount(onlineUsers) |
chat:message |
addLiveChatMessage (100 messages au plus, sans doublon) |
draw:winner |
setWinnerAnnouncement, puis invalidation des tags Lottery et Wallet |
points:awarded |
Fenêtre « +n EtherPoints » si userId correspond au compte |
| Fonction exposée | Émission |
|---|---|
sendChatMessage(message, emoji) |
chat:send si la connexion est active ; renvoie false sinon, et RoomChat affiche « Connexion à la salle en cours. Réessayez dans un instant. » sans ajouter le message |
sendReaction(emoji) |
chat:react, jamais appelée |
Le slice lottery démarre avec une discussion vide et un compteur de présence à zéro : les messages d'accueil codés en dur ont été retirés.
Écarts restants avec le web :
- l'état de connexion n'est pas suivi (pas de
socketConnected) : la discussion ne signale une coupure qu'au moment d'un envoi ; - la connexion n'est pas fermée à la déconnexion du compte.
points:awarded est diffusé par le serveur à tous les clients avec l'identifiant interne du compte ; le filtrage se fait dans l'application. Ce point de confidentialité est commun au web et au mobile.
Séquence de la salle d'attente au tirage
sequenceDiagram participant W as WaitingRoomScreen participant L as LiveDrawScreen participant H as useSocket participant S as Serveur Socket.IO participant R as Store Redux W->>H: useSocket roundId, phase WAITING_ROOM H->>S: connexion avec auth.token H->>S: draw:join roundId S-->>H: draw:waiting-room H->>R: setWaitingRoomOnlineCount S-->>H: chat:message H->>R: addLiveChatMessage W->>W: phase DRAWING, replace LiveDraw W->>H: démontage, socket.off et draw:leave L->>H: useSocket roundId, phase DRAWING H->>S: draw:join roundId S-->>H: draw:winner H->>R: setWinnerAnnouncement, invalidateTags Lottery Wallet R-->>L: arrêt de la roue sur le numéro gagnant
Notifications
mobile-app/src/hooks/usePushNotifications.ts porte un nom trompeur : il ne gère aucune notification push.
| Membre | Rôle |
|---|---|
notifications, isLoading, isFetching, isError, refetchNotifications |
Résultat de GET /user/notifications, demandé seulement si isAuthenticated |
unreadCount, markAllAsSeen |
Notifications non lues ; markAllAsSeen mémorise les identifiants vus dans une variable du module |
osPushBanner, triggerSimulatedOsPush(title, body) |
Bandeau en haut de l'écran, masqué après 4,5 secondes ; plus appelé |
L'accueil, « Mes tickets » et le titre des onglets Profil et Parrains affichent HeaderActions : la cloche NotificationButton, avec une pastille quand unreadCount est positif, à côté de la roue des paramètres. L'appui sur la cloche ouvre NotificationsScreen.tsx. Cet écran liste les notifications, permet de tirer pour rafraîchir et marque tout comme vu à sa fermeture. L'état « vu » n'est ni envoyé au backend ni enregistré sur l'appareil : il est perdu au redémarrage de l'application.
Le backend renvoie aujourd'hui une liste fixe de trois notifications, générée par UserService.getPushNotificationsHistory dans backend/src/api/user/user.services.ts. Chaque notification porte une catégorie (drawReminders, results, rewards ou referrals) ; celles dont la catégorie est coupée dans Paramètres, puis Notifications (NotificationSettingsScreen.tsx) sont retirées de la réponse. L'interrupteur général pushNotificationsEnabled est enregistré mais ne filtre pas la liste. La liste est rechargée par le geste de rafraîchissement de l'accueil et de l'écran Notifications.
Brancher de vraies notifications push
Les étapes, non réalisées dans le projet, seraient :
- ajouter le plugin
expo-notificationsdansapp.json, puis demander l'autorisation à l'utilisateur après la connexion ; - obtenir un jeton Expo Push (ce qui suppose un projet EAS, voir Builds et publication) et l'envoyer au backend par une nouvelle route ;
- stocker ce jeton côté serveur et l'utiliser depuis un worker pour les événements utiles (ouverture de la salle d'attente, résultat, cadeau reçu, décision sur la vérification d'âge) ;
- respecter la préférence
pushNotificationsEnabled, les catégories depreferences.notificationset l'opposition de l'utilisateur.
Pour un nouvel événement temps réel, suivre Ajouter un événement temps réel.