chatbuss_chat 1.4.0
chatbuss_chat: ^1.4.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_selector, le
plugin officiel de Flutter. Depuis la 1.2.0, le SDK ne dépend plus de
file_picker : ton application peut utiliser la version de file_picker de son
choix sans conflit.
Formats acceptés (depuis la 1.4.0, ceux de la console ChatBuss, sans l'audio ni
le zip) : images JPEG, PNG, WebP, GIF (5 Mo), vidéos MP4, MOV, WebM,
MPEG (50 Mo), documents PDF, Word, Excel, PowerPoint, TXT, CSV (10 Mo) — le
serveur vérifie le contenu réel du fichier, pas seulement son extension.
Sous Android, le sélecteur par défaut ne propose pas les vidéos :
file_selector_android charge le fichier entier en mémoire avant de le rendre,
et une vidéo de téléphone pourrait faire tomber ton application. Pour que tes
utilisateurs Android en envoient, branche un sélecteur qui vérifie la taille
avant de lire (ci-dessous, image.length() avant readAsBytes()). Ailleurs, le
sélecteur par défaut refuse un fichier trop lourd sans le charger.
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 #
Quand un conseiller répond, l'utilisateur reçoit une notification — application fermée, en arrière-plan ou ouverte. Le SDK ne touche pas à Firebase : ton application garde sa propre initialisation, récupère son jeton FCM et le passe au SDK, qui l'enregistre auprès de ChatBuss et le retire à la déconnexion.
// L'app hôte récupère son jeton (firebase_messaging est déjà configuré chez elle) :
final fcmToken = await FirebaseMessaging.instance.getToken();
Navigator.push(context, MaterialPageRoute(builder: (_) => ChatbussChatScreen(
serverUrl: 'https://ton-serveur',
channelId: 'TON-CHANNEL-ID',
user: ChatUser(externalUserId: me.id, name: me.name),
deviceToken: fcmToken, // ← active le push
platform: 'android', // ou 'ios'
)));
Qui envoie la notification #
Ce jeton appartient au projet Firebase de ton application. ChatBuss ne peut
pas l'utiliser depuis son propre projet : Firebase refuse l'envoi
(SenderIdMismatch) et rien n'arrive. Le choix se fait sur le canal, dans
Canaux → ton canal App Chat → Réglages → Envoi des notifications :
- Par votre serveur (relais) — à chaque réponse, ChatBuss appelle une route
HTTPS de ton serveur, qui envoie la notification avec son propre Firebase. Tu ne
confies aucun identifiant. L'appel porte l'
externalUserIdde l'utilisateur, ses jetons, le texte de la réponse et le badge. - Par ChatBuss (Firebase) — tu colles le JSON d'un compte de service de ton
projet, créé pour l'occasion avec le seul rôle Firebase Cloud Messaging API
Admin. Pas la clé
firebase-adminsdk, qui ouvre tout le projet.
Format de l'appel, vérification de la signature et dépannage : chatbuss.com/documentation/#push.
Application ouverte #
Au premier plan, ni Android ni iOS n'affichent d'eux-mêmes une notification Firebase : elle arrive dans ton application, qui doit l'afficher.
- iOS : une ligne au démarrage suffit.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true); - Android : écoute
FirebaseMessaging.onMessageet affiche toi-même la notification (avecflutter_local_notifications, par exemple), sur le canal déclaré dans Canal de notification Android. Ce champ attend l'identifiant du canal (support_messages), pas son libellé : sinon Android range la notification dans un canal par défaut, sans son ni priorité.
Si l'utilisateur avait la conversation sous les yeux dans les 15 dernières
secondes, la notification part quand même, avec visitor_present à vrai (dans
l'appel du relais, ou "1" dans les data Firebase) : à toi de choisir si tu
affiches une bannière par-dessus.
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 |
Utilisateur non connecté #
Quand l'application hôte ne fournit pas d'identité et que le canal en exige une,
le SDK affiche son propre formulaire. Ce que l'utilisateur y saisit — nom, email,
téléphone — est mémorisé sur l'appareil : à la réouverture du support, il
retrouve sa conversation au lieu de repartir de zéro. La signature userHash,
elle, n'est jamais écrite sur l'appareil.
Une identité fournie par l'application hôte prime toujours sur cette trace locale : elle connaît l'état courant de son utilisateur, la trace ne connaît que le passé.
À la déconnexion de votre utilisateur, appelez
ChatbussClient.reset(). Session, file d'attente, identité mémorisée et jeton push partent ensemble. Sans cela, sur un appareil partagé — une tablette en boutique — le compte suivant reprendrait la conversation du précédent.
Écran distant (1.1.0) #
Un canal peut servir un écran distant : la conversation est alors une page servie par le serveur ChatBuss, affichée plein écran dans l'application. Ses corrections sont déployées côté ChatBuss et visibles à la prochaine ouverture du support, sans nouvelle version de l'application.
- Rien à écrire côté application.
ChatbussChatScreenetChatbussChat.instance.open()choisissent l'écran d'après le réglage du canal. - Activation, côté ChatBuss : réglage
ecrandu canal à"web". Toute autre valeur, ou son absence, garde l'écran natif. - Repli automatique sur l'écran natif si la page ne peut pas servir (page
injoignable, ouverture de plus de 15 s, session impossible, plateforme sans
WebView). Remettre le réglage à
"natif"ramène tout le parc sur l'écran natif, sans passer par les stores. - Garder l'écran natif en toutes circonstances :
ChatbussChatScreen(allowRemoteScreen: false, ...). - La page ne reçoit aucun identifiant : elle échange un ticket à usage unique, remis par JavaScript, contre un cookie de session inaccessible aux scripts. Les liens externes s'ouvrent dans le navigateur du système.
- Une identité fournie par l'application mais incomplète pour le canal garde l'écran natif, dont le formulaire la complète sans séparer l'historique.
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.
Après une coupure — tunnel, changement de réseau, mise en veille — le SDK demande un ticket de connexion neuf et se reconnecte, en espaçant ses tentatives (5 s, puis 10, 20, 40, 60 au plus). Rien à appeler : un retour au premier plan relance immédiatement.