feat(postit): keep Android SignalR chat in background without ongoing notification
Share the chat transport between UI, calls and an opt-in background service. Restore account rooms and attempt boot restart, keeping event alerts independent from connection permissions. Document Android limits and add lifecycle regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a39c3912ea
commit
311d55dcc6
32 changed files with 2210 additions and 143 deletions
|
|
@ -23,6 +23,7 @@ Chaque sujet est détaillé dans sa propre page (sous
|
|||
| Dictionnaires métier (héritage en arbre) | [dictionnaires-metier.md](architecture/dictionnaires-metier.md) |
|
||||
| Offre fournisseur + frontmatter (`ClasseFormulaire`, `ClasseDevis`, parsing) | [offres-frontmatter.md](architecture/offres-frontmatter.md) |
|
||||
| PostIt / OIDC (client desktop, custom URI scheme, silent refresh) | [postit-oidc.md](architecture/postit-oidc.md) |
|
||||
| Notifications Web/PostIt, ChatHub et service Android SignalR | [notifications.md](architecture/notifications.md) |
|
||||
|
||||
## Stack technique
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ clients.
|
|||
| Annonce persistée | Entité `Notification` en base, API HTTP | Affichée dans le layout et masquable | Liste sur `HomePage`, chargée par HTTP et masquable |
|
||||
| Message d'interface | Liste de portée requête dans `ViewBag.Notify` | Affichée comme alerte dans le layout | Non consommé |
|
||||
| Événement de chat | Hub SignalR authentifié à `/chathub` | Le site Web n'en consomme pas dans ses vues MVC | `notifyUser` et `notifyRoom` alimentent les journaux du chat |
|
||||
| Événement métier `push` | SignalR vers les connexions actives, plus e-mail | Pas de consommateur identifié dans les vues Web | Pas de gestionnaire `push` dans le client PostIt actuel |
|
||||
| Événement métier `push` | SignalR vers les connexions actives, plus e-mail | Pas de consommateur identifié dans les vues Web | Alerte native par le service Android ; liste persistée sur l'accueil via HTTP |
|
||||
| Alerte native Android | Connexion SignalR existante | Sans objet | Service de fond facultatif ; notification seulement à réception d'un événement |
|
||||
|
||||
Les notifications persistées sont donc différentes d'une notification système
|
||||
native (notification du navigateur ou du système d'exploitation). Le client
|
||||
PostIt affiche une liste dans sa page d'accueil ; il ne demande pas de
|
||||
permission de notification native.
|
||||
PostIt affiche une liste dans sa page d'accueil. Sur Android, le service de
|
||||
messagerie peut aussi afficher des notifications natives, après autorisation.
|
||||
|
||||
## 1. Annonces persistées
|
||||
|
||||
|
|
@ -173,6 +174,7 @@ Les événements de notification émis par le hub comprennent notamment :
|
|||
| `notifyRoom` | `type`, salon, utilisateur ou détail | Ajout au journal du salon ; met à jour la liste d'utilisateurs sur `UserJoin` |
|
||||
| `joint` | `ChatRoomInfo` | Initialise la vue du salon rejoint |
|
||||
| `ReceiveMessage` | message avec nom, salon et corps | Ajoute le message à l'historique du salon |
|
||||
| `addPV` | expéditeur, texte privé | Ajoute le message privé au journal système ; alerte native Android en arrière-plan |
|
||||
| `notifyUserInRoom` | `type`, salon, message | Pas de gestionnaire correspondant dans `ChatViewModel` actuellement |
|
||||
|
||||
Les constantes de types du chat sont déclarées dans
|
||||
|
|
@ -181,23 +183,128 @@ Les messages `notifyUser` et `notifyRoom` ne sont pas enregistrés en base :
|
|||
ils décrivent l'état temps réel de connexion, de salon ou les erreurs de
|
||||
conversation.
|
||||
|
||||
Le [`ChatViewModel`](../../src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs)
|
||||
Le singleton
|
||||
[`ChatHubConnection`](../../src/PostIt/PostIt/Services/ChatHubConnection.cs)
|
||||
ouvre le hub avec le jeton d'accès PostIt, tente un renouvellement silencieux
|
||||
du jeton, et active la reconnexion automatique. Après reconnexion, il rejoint
|
||||
à nouveau le salon sélectionné, car la connexion SignalR précédente et ses
|
||||
groupes ne sont plus actifs. Les callbacks mettent à jour les collections sur
|
||||
le thread UI d'Avalonia.
|
||||
du jeton et active la reconnexion automatique. Il est partagé par le service
|
||||
Android, le
|
||||
[`ChatViewModel`](../../src/PostIt/PostIt/ViewModels/Chat/ChatViewModel.cs)
|
||||
et la signalisation des appels : ouvrir la page ne crée pas une seconde
|
||||
connexion.
|
||||
|
||||
Le transport conserve en mémoire les 500 dernières livraisons de messages
|
||||
privés et de salon pour les présenter si l'interface s'ouvre après le
|
||||
démarrage du service. Ce tampon est borné, non persisté sur disque et vidé
|
||||
à l'arrêt de la connexion ou au changement de compte. Il ne permet pas de
|
||||
récupérer les messages envoyés lorsque le socket était déconnecté.
|
||||
|
||||
Les salons sont mémorisés localement, par hôte et compte, dans
|
||||
`Settings.ChatRoomsByAccount`. Le transport les rejoint à nouveau après une
|
||||
reconnexion ou un redémarrage du processus, sans dépendre de l'interface
|
||||
Avalonia. « Quitter le salon » supprime cette mémorisation. Cela n'ajoute pas
|
||||
d'abonnement serveur hors ligne ni d'historique des messages manqués.
|
||||
Les callbacks destinés aux collections de l'interface sont dispatchés sur
|
||||
le thread UI et les callbacks obsolètes sont ignorés après déconnexion.
|
||||
|
||||
### Événement métier `push`
|
||||
|
||||
En plus des événements du chat, `YavscMessageSender` utilise le même contexte
|
||||
SignalR pour émettre `push` avec le topic et une charge utile sérialisée. Ce
|
||||
nom d'événement est distinct de `notifyUser` et `notifyRoom`. Aucun
|
||||
gestionnaire `push` n'est présent dans le client PostIt actuel : les annonces
|
||||
métier persistées sont visibles dans PostIt lors du prochain chargement ou
|
||||
rafraîchissement HTTP.
|
||||
nom d'événement est distinct de `notifyUser` et `notifyRoom`. Le service
|
||||
Android peut en afficher une alerte native ; les annonces métier persistées
|
||||
restent visibles dans PostIt lors du prochain chargement ou rafraîchissement
|
||||
HTTP.
|
||||
|
||||
## 5. Points d'attention pour les évolutions
|
||||
## 5. Android : connexion SignalR en arrière-plan
|
||||
|
||||
### Réglage utilisateur et durée de vie
|
||||
|
||||
`AndroidKeepChatConnected`, désactivé par défaut, est éditable dans les
|
||||
paramètres Android :
|
||||
« Garder la connexion au serveur active et la reprendre au démarrage du
|
||||
téléphone ». Enregistrer les paramètres pour appliquer le changement.
|
||||
|
||||
- **Désactivé** : le chat reste connecté pendant la vie du processus PostIt,
|
||||
même si l'activité est masquée, mais rien ne garantit que ce processus
|
||||
survive en arrière-plan. Les messages alimentent le journal ; les alertes
|
||||
natives en arrière-plan nécessitent le service activé.
|
||||
- **Activé** : un service Android ordinaire héberge la même connexion
|
||||
SignalR, **sans notification permanente**. Un récepteur
|
||||
`BOOT_COMPLETED` tente de reprendre ce service si l'option et une session
|
||||
enregistrée sont encore disponibles.
|
||||
- La fermeture d'une page ou de l'activité ne ferme pas la messagerie.
|
||||
La déconnexion du compte arrête le service et la connexion avant la
|
||||
suppression des jetons. Les appels et captures caméra/micro restent
|
||||
soumis à leur propre cycle de vie.
|
||||
- Le service peut démarrer sans construire l'interface Avalonia. L'ouverture
|
||||
ultérieure de l'application réutilise le même conteneur et le même
|
||||
transport, sans connexion doublonnée.
|
||||
|
||||
Le service n'appelle ni `StartForegroundService` ni `StartForeground` et ne
|
||||
publie aucune notification de connexion/reconnexion. Pour l'arrêter,
|
||||
désactiver l'option et enregistrer les paramètres, ou déconnecter le compte.
|
||||
Les notifications ne sont créées que lors de nouveaux messages ou événements ;
|
||||
elles disparaissent à leur ouverture et ne sont pas marquées permanentes.
|
||||
Android 13+ demande l'autorisation `POST_NOTIFICATIONS` pour ces alertes
|
||||
visibles. Un refus empêche les alertes, **pas la connexion SignalR**.
|
||||
Les échecs de démarrage sont signalés, sans basculer silencieusement vers
|
||||
un service de premier plan et sans répéter une permission refusée.
|
||||
|
||||
### Notifications et limites
|
||||
|
||||
Le service écoute les messages privés, les messages des salons rejoints et
|
||||
l'événement métier SignalR `push`. En arrière-plan, il affiche des alertes
|
||||
locales génériques pour ne pas exposer de contenu privé sur l'écran
|
||||
verrouillé. Il ne faut pas confondre cet événement SignalR nommé `push` avec
|
||||
un service de push externe.
|
||||
|
||||
**Aucun Firebase/FCM, serveur Google de notifications, jeton d'appareil,
|
||||
configuration de projet ou migration push n'est requis.** Les API métier et
|
||||
le ChatHub existants restent les seuls services réseau utilisés pour la
|
||||
messagerie.
|
||||
|
||||
Cette solution est de type *best effort*, pas une connexion immortelle :
|
||||
|
||||
- Android, Doze, l'absence de réseau et les politiques des constructeurs
|
||||
peuvent interrompre le socket ou empêcher le redémarrage du service.
|
||||
Autoriser les données en arrière-plan permet le trafic réseau ; désactiver
|
||||
manuellement l'optimisation de batterie pour PostIt assouplit les limites
|
||||
de Doze. Ces réglages ne garantissent pas la survie du processus ou du
|
||||
service et ne suppriment pas les autres limites d'exécution Android.
|
||||
- Android 8+ peut arrêter un service ordinaire lorsque l'application reste
|
||||
en arrière-plan, ou refuser `StartService` depuis le récepteur de démarrage.
|
||||
Dans ce cas, PostIt signale le refus et attend une nouvelle ouverture de
|
||||
l'application ; aucune tentative de contournement par service de premier
|
||||
plan, tâche périodique ou exemption automatique n'est ajoutée.
|
||||
- Après un arrêt forcé par l'utilisateur, rouvrir PostIt ; le démarrage du
|
||||
téléphone n'est pas un contournement de cet arrêt.
|
||||
- Le renouvellement nécessite une session encore valide. Une session
|
||||
révoquée ou sans jeton de renouvellement exige une reconnexion.
|
||||
- Les messages de chat sont éphémères : les messages envoyés pendant une
|
||||
interruption ne sont pas récupérés par cette fonctionnalité. Les
|
||||
notifications métier persistées restent accessibles par l'API.
|
||||
- Aucun type `specialUse` ni permission `FOREGROUND_SERVICE_SPECIAL_USE`
|
||||
n'est déclaré pour la messagerie. Les services de premier plan des appels
|
||||
et captures caméra/micro restent inchangés : ils ne sont pas utilisés pour
|
||||
maintenir le chat.
|
||||
|
||||
### Validation sur téléphone
|
||||
|
||||
Les tests locaux vérifient le partage de connexion, la restauration des
|
||||
salons et le nettoyage de session. Ils ne remplacent pas une validation
|
||||
Android réelle : absence de notification lors de l'activation et des
|
||||
reconnexions, notification seulement à réception d'un événement activité
|
||||
masquée, retour dans l'interface sans second socket, désactivation dans les
|
||||
paramètres, déconnexion/reconnexion, coupure et retour réseau, veille
|
||||
prolongée, redémarrage du téléphone et refus de permission sans arrêt du chat.
|
||||
|
||||
Une activité de vérification est incluse uniquement dans l'APK Debug :
|
||||
`fr.pschneider.postit.ChatServiceTestActivity`. Son résultat est écrit dans
|
||||
le fichier privé de l'application `files/chat-service-test-result.txt`.
|
||||
Elle vérifie la composition du conteneur partagé, pas la réception réelle
|
||||
après un démarrage ou une longue veille.
|
||||
|
||||
## 6. Points d'attention pour les évolutions
|
||||
|
||||
- Conserver les règles d'audience alignées entre l'API et le site Web ; en
|
||||
particulier, appliquer un filtrage `Target` côté Web avant d'exposer les
|
||||
|
|
|
|||
Loading…
Reference in a new issue