chatbuss_chat 1.0.0
chatbuss_chat: ^1.0.0 copied to clipboard
Support client in-app pour applications Flutter — messagerie temps réel, pièces jointes et enquête de satisfaction, adossés à la plateforme ChatBuss.
chatbuss_chat — SDK de chat in-app ChatBuss (Flutter) #
Permet à n'importe quelle application mobile d'offrir un support client (messagerie + IA + agents) via ChatBuss — l'équivalent du widget web livechat, mais pour les apps.
Comment ça marche #
Le SDK parle à la façade mobile, /api/v1/app-chat/v1/* — distincte de celle du
widget web, et versionnée à dessein : une application publiée met des jours à passer
les stores, là où un site se redéploie en une minute.
| Étape | Endpoint |
|---|---|
| Ouvrir/reprendre la session | POST /session (renvoie un user_id persisté) |
| Envoyer un message | POST /messages |
| Recevoir les réponses agent | WS /api/v1/ws/app-chat/v1, avec relecture de GET /messages/{user_id} en filet |
Chaque appel porte l'en-tête X-Chatbuss-SDK : le serveur sait ainsi quelle génération
d'application lui parle, et peut répondre 426 à une version périmée plutôt que de
deviner. Le user_id est stocké (SharedPreferences, clé chatbuss_visitor_<channel_id>)
→ la conversation reprend au relancement de l'app.
Prérequis côté ChatBuss #
Le canal doit être de type
app_chat, paslivechat. Les deux servent le même noyau, mais par des façades séparées :/api/v1/app-chat/v1/*pour les applications,/api/v1/livechat/*pour le widget web. Un site se redéploie en une minute, une application publiée met des jours à passer les stores et une partie du parc ne se met jamais à jour — les deux ne peuvent pas partager un contrat. Un identifiant de canallivechatest refusé par le SDK.
- Créer un canal de type
app_chatdans la console ChatBuss. - Récupérer son
channel_id(UUID public). - Le donner à l'app hôte (avec l'URL du serveur).
Intégration (côté app cliente) #
flutter pub add chatbuss_chat
# ou à la main, dans le pubspec.yaml de l'app hôte
dependencies:
chatbuss_chat: ^1.0.0
import 'package:chatbuss_chat/chatbuss_chat.dart';
// Ouvrir le support depuis un bouton "Aide" :
Navigator.push(context, MaterialPageRoute(
builder: (_) => ChatbussChatScreen(
serverUrl: 'https://chatbuss-server.h.gandyam.com',
channelId: 'VOTRE-CHANNEL-ID',
// L'app connaît déjà son utilisateur → pré-identification, pas de formulaire :
user: ChatUser(name: 'Awa Diop', email: 'awa@exemple.com'),
),
));
C'est tout. L'utilisateur discute avec le support sans quitter l'app.
Tester l'intégration (app d'exemple) #
cd chatbuss_chat_sdk/example
# 1) Éditer lib/main.dart : mettre kServerUrl et kChannelId
flutter pub get
flutter run # sur un émulateur ou un téléphone branché
Puis, côté ChatBuss (console agent), réponds au message : il apparaît dans l'app (polling ~3 s).
Checklist de validation :
- ❌ Le chat s'ouvre et affiche le message d'accueil.
- ❌ L'envoi d'un message → il apparaît + arrive dans la console agent ChatBuss (canal
app_chat). - ❌ La réponse de l'agent apparaît dans l'app en quelques secondes.
- ❌ Fermer/rouvrir l'app → la conversation est reprise (même
visitor_id).
API du SDK #
ChatbussChatScreen(...)— écran de chat clé en main.ChatbussClient(...)— client bas niveau si tu veux ta propre UI :init()·sendMessage(text)·fetchMessages()·getConfig()sendMedia(bytes:, filename:, mimeType:)— pièce jointeanswerSurvey(surveyId:, rating:, comment:)— note de satisfaction
Enquête de satisfaction (CSAT) #
Rien à câbler : si le canal a csat_enabled, l'enquête arrive dans le fil comme
un message et ChatbussChatScreen l'affiche en étoiles cliquables. Pour une
interface sur mesure, ChatMessage.isSurvey et ChatMessage.survey portent le
barème, et ChatbussClient.answerSurvey enregistre la note.
Pièces jointes #
Le bouton trombone est présent par défaut et s'appuie sur file_picker.
Formats acceptés : JPEG, PNG, WebP, GIF, PDF, 10 Mo maximum — le serveur
vérifie la signature réelle du fichier, pas seulement son extension.
Si ton application possède déjà un sélecteur, branche-le pour ne pas en avoir deux :
ChatbussChatScreen(
serverUrl: kServerUrl,
channelId: kChannelId,
pickAttachment: () async {
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
if (image == null) return null; // annulé
return ChatPickedFile(
bytes: await image.readAsBytes(),
filename: image.name,
// Le serveur refuse application/octet-stream : déduis le type du nom.
mimeType: mimeDepuisNom(image.name),
);
},
)
Pour une interface sur mesure, ChatbussClient.sendMedia ne demande que des
octets — aucun sélecteur imposé.
À savoir : une pièce jointe dont l'envoi échoue ne survit pas à la fermeture de l'application. Le texte, lui, est conservé et rejoué au redémarrage.
Notifications push (Phase 2 — disponible) #
Quand l'app est fermée, le polling ne suffit pas. Le SDK enregistre le jeton push
de l'appareil ; quand un agent répond, chatbuss_server envoie une notification push.
Principe : l'app hôte possède déjà Firebase → elle obtient son jeton FCM et le passe au SDK. Le SDK ne gère pas l'init Firebase (pour ne pas entrer en conflit).
// L'app hôte récupère son jeton (elle a déjà firebase_messaging configuré) :
final fcmToken = await FirebaseMessaging.instance.getToken();
Navigator.push(context, MaterialPageRoute(builder: (_) => ChatbussChatScreen(
serverUrl: 'https://ton-serveur',
channelId: 'TON-CHANNEL-ID',
user: ChatUser(name: 'Awa', email: 'awa@exemple.com'),
deviceToken: fcmToken, // ← active le push
platform: 'android', // ou 'ios'
)));
Côté serveur, c'est déjà branché :
POST /api/v1/app-chat/v1/device-token— enregistre le jeton (stocké sur leClient) ;/device-token/unregisterle retire à la déconnexion de l'utilisateur.- Tâche
send_visitor_push— déclenchée automatiquement à chaque réponse agent/bot (hook centralnotify_new_message). Réutilise l'infra FCM existante (fcm_service). No-op siFCM_ENABLEDest faux.
Prérequis : FCM_CREDENTIALS_JSON configuré côté serveur, et Firebase configuré
dans l'app hôte (google-services.json / GoogleService-Info.plist).
Vérification de l'identité (recommandé) #
Par défaut, l'identité transmise dans ChatUser est crue sur parole. Connaître
l'email d'un utilisateur suffit alors à ouvrir une session en son nom et à lire son
historique de support. Tant que l'option est désactivée, le comportement reste celui-ci.
Pour la fermer, active « Exiger une identité signée » sur le canal, dans la console ChatBuss, et renseigne un secret partagé.
Ce que ton backend doit calculer #
import hmac, hashlib
user_hash = hmac.new(
CHATBUSS_IDENTITY_SECRET.encode(),
str(user.id).encode(), # le sujet — voir l'ordre ci-dessous
hashlib.sha256,
).hexdigest()
Le sujet signé suit un ordre figé, qui fait partie du contrat :
external_user_id, sinon email, sinon phone. Autrement dit, dès que tu fournis
externalUserId, c'est lui qui doit être signé.
Ce que l'application fait de la signature #
ChatbussChatScreen(
serverUrl: 'https://<api>',
channelId: '<channel_id>',
user: ChatUser(
externalUserId: me.id, // pivot d'identité, stable même si l'email change
name: me.name,
email: me.email,
userHash: me.chatbussHash, // récupéré auprès de TON backend
),
)
Le secret ne doit jamais être embarqué dans l'application. Un APK se décompile : quiconque en extrait le secret peut signer l'identité de n'importe quel client. L'application demande la signature à son propre serveur, au moment de la connexion de l'utilisateur.
Erreurs possibles #
| Situation | Réponse |
|---|---|
| Signature valide | 200 |
| Signature absente alors que le canal l'exige | 403 Identité non vérifiée. |
| Signature d'un autre utilisateur | 403 |
| Canal en vérification sans secret configuré | 403 — échec fermé, volontaire |
| Aucune identité fournie (visiteur anonyme) | 200, rien à vérifier |
Limites connues #
- Le SDK ne fait pas l'init Firebase : c'est à l'app hôte de fournir le jeton.
- Une pièce jointe dont l'envoi échoue ne survit pas à la fermeture de l'application. Le texte, lui, est conservé et rejoué au redémarrage.
Temps réel #
Le WebSocket est utilisé quand il est disponible, et la relecture périodique de l'historique reste le filet — elle passe simplement de 3 s à 20 s une fois la connexion établie. Aucun réglage n'est nécessaire : un réseau qui bloque le WebSocket dégrade le délai, jamais la réception.