Aller au contenu

Widget & SDK

Côté visiteur, l’intégration se fait en deux temps : créer une session visiteur (qui rend un jeton signé de courte durée), puis ouvrir un WebSocket authentifié par ce jeton. Les SDK font cet enchaînement pour vous.

  1. Le widget appelle GET /create/visitor-session (ou GET /fetch/widget-config d’abord, pour rendre le lanceur sans encore créer de session).
  2. Le relais renvoie un jeton visiteur (JWT, ~1 h) lié à (tenant_id, widget_public_key, origin), plus la config publique du widget.
  3. Le widget ouvre WSS /ws/widget?token=<jwt> et reçoit une trame welcome.
  4. À l’expiration, le widget appelle POST /refresh/visitor-token (en Bearer l’ancien jeton, même expiré) pour reprendre la même conversation — la session Redis vit beaucoup plus longtemps (~30 jours) que le JWT.

POST /create/visitor-session est public mais CORS-gated : le relais valide origin contre les allowed_origins du tenant (sinon 403).

Fenêtre de terminal
curl $BASE_URL/api/v1/create/visitor-session \
-X POST \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"widget_public_key": "pk_...",
"origin": "https://app.acme.com",
"locale": "fr"
}'
{
"status_code": 201,
"data": {
"session_id": "vs_...",
"visitor_token": "<jwt>",
"expires_at": "2026-06-21T11:00:00Z",
"widget_config": { "tenant_name": "Demo Boutique", "is_online": true, "...": "..." }
},
"message": "Visitor session created."
}

Champs du corps : tenant_id, widget_public_key, origin (requis), locale (défaut fr), metadata (contexte libre), routing_key (file de destination, ex. un store id) et mode (bot par défaut, ou human pour aller droit à la file opérateur).

Le WebSocket visiteur n’est pas un endpoint HTTP classique : le jeton passe en paramètre de requête (?token=<jwt>), car un navigateur ne peut pas poser d’en-tête personnalisé sur la poignée de main d’un upgrade WebSocket.

WSS $BASE_URL/api/v1/ws/widget?token=<visitor_jwt>

Codes de fermeture applicatifs à gérer côté client :

  • 4401 — jeton visiteur invalide (re-créez/rafraîchissez une session).
  • 4404 — session expirée côté serveur (re-créez une session).

Les trames sont du JSON discriminé par un champ type. Quelques exemples issus du protocole :

SenstypeRôle
visiteur → serveurmsgEnvoyer un message (client_msg_id + content)
visiteur → serveurtypingIndicateur de frappe (state: start|stop)
visiteur → serveurpingBattement de cœur (le serveur répond pong)
serveur → visiteurwelcomePremière trame après connexion (session + config)
serveur → visiteurackAccusé de réception d’un message (écho client_msg_id)
serveur → visiteurmsgMessage du bot, d’un agent ou du système
serveur → visiteurtypingFrappe du bot ou de l’agent

Vous n’avez normalement pas à parler ce protocole à la main : les SDK l’enveloppent.

Bibliothèque Angular à composants standalone, service à base de signaux, compatible SSR. Cible Angular 17+.

Fenêtre de terminal
npm install @bloonio/chat-angular @bloonio/chat-client-core

Importez la feuille de style une fois dans styles.css :

@import "@bloonio/chat-angular/styles.css";

Déclarez le fournisseur :

app.config.ts
import { provideBloonioChat } from '@bloonio/chat-angular';
export const appConfig = {
providers: [
provideBloonioChat({
tenantId: '019e4ae7-1a2b-7c3d-8e4f-5a6b7c8d9e0f',
publicKey: 'pk_...',
baseUrl: '$BASE_URL',
locale: 'fr',
// Optionnel — lier les sessions anonymes à vos utilisateurs.
// Votre backend calcule HMAC-SHA256(tenant_secret, user_id).
// visitorTokenProvider: () => ... // renvoie "userId:signature"
}),
],
};

Trois modes de rendu, qui partagent tous le même BloonioChatService :

ModeComposantQuand
Flottant<bloonio-chat-launcher />Bulle dans le coin qui s’ouvre au clic.
Panneau ancré<bloonio-chat-panel mode="docked" />Chat permanent dans une colonne dédiée.
Fil sans chrome<bloonio-chat-thread />Juste les messages + la saisie, à intégrer.

Accès programmatique via le service :

import { inject } from '@angular/core';
import { BloonioChatService } from '@bloonio/chat-angular';
const chat = inject(BloonioChatService);
chat.unreadCount; // Signal<number>
chat.botTyping; // Signal<boolean>
chat.openPanel();
chat.sendMessage('Où est ma commande ?');

Le bootstrap (fetchWidgetConfig) est différé via afterNextRender, donc le rendu serveur ne fait aucun appel réseau ; les composants se connectent à l’hydratation.

Pour lier une session anonyme à l’un de vos utilisateurs connectés, appelez POST /identify/visitor en Bearer le jeton visiteur, avec une user_signature calculée côté serveur : hex(hmac_sha256(tenant_secret, user_id)). La signature serveur empêche un script malveillant de forger des identités.