Quickstart
Ce guide vous mène du login d'un senior jusqu'à la réception du premier event widget (météo, news, quiz) publié par le back-end sur le canal DataChannel LiveKit, en cinq étapes.
Prérequis
- Bun 1.3+ installé localement
- Accès à l'API dev (URL fournie par Bruno,
https://<api-dev-domain>) - Token Scaleway dev si vous souhaitez tester contre l'environnement déployé
- Un client LiveKit (côté tablette
@livekit/react-native, ailleurslivekit-client)
Étape 1, login senior et récupération du JWT
Le back-end expose un endpoint REST POST /v1/auth/login qui retourne une paire accessToken / refreshToken au format JWT.
type LoginResponse = {
accessToken: string;
refreshToken: string;
expiresIn: number;
};
async function loginSenior(
apiBaseUrl: string,
email: string,
password: string
): Promise<LoginResponse> {
const response = await fetch(`${apiBaseUrl}/v1/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, role: "senior" }),
});
if (!response.ok) {
throw new Error(`Login failed: HTTP ${response.status}`);
}
return (await response.json()) as LoginResponse;
}L'accessToken est un JWT de courte durée (15 minutes par défaut). Le refreshToken (7 jours) sert à le renouveler (cf. Authentification).
Étape 2, récupérer un token d'accès LiveKit
Le back-end signe les tokens LiveKit côté serveur, la tablette ne stocke jamais l'API secret. L'endpoint POST /v1/livekit/token retourne un token JWT scope participant lié à une room dédiée au senior.
type LivekitTokenResponse = {
token: string;
url: string; // wss://livekit-dev.195-154-239-8.sslip.io
roomName: string; // le senior_id, forcé côté serveur depuis le JWT
identity: string; // le senior_id également (le client ne choisit ni l'un ni l'autre)
};
async function fetchLivekitToken(
apiBaseUrl: string,
accessToken: string
): Promise<LivekitTokenResponse> {
const response = await fetch(`${apiBaseUrl}/v1/livekit/token`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({}),
});
if (!response.ok) {
throw new Error(`LiveKit token request failed: HTTP ${response.status}`);
}
return (await response.json()) as LivekitTokenResponse;
}Le contrat REST complet est typé dans @kohami/shared :
import type {
LivekitTokenRequest,
LivekitTokenResponse,
} from "@kohami/shared";Étape 3, joindre la room et souscrire au DataChannel
Côté tablette (React Native), utilisez @livekit/react-native. L'exemple ci-dessous est en livekit-client pour la clarté, l'API est identique.
import { Room, RoomEvent } from "livekit-client";
const room = new Room({
adaptiveStream: true,
dynacast: true,
});
await room.connect(livekitUrl, livekitToken);
room.on(RoomEvent.DataReceived, (payload, _participant, _kind, topic) => {
const decoded = new TextDecoder().decode(payload);
console.log(`[${topic}]`, decoded);
});Le back-end publie sur trois familles de topics (voir Realtime voice et Data channels pour la liste complète) :
kohami.widget.data, events outbound (résultats météo, news, quiz, vision, assistance)kohami.user.*, events inbound (actions tablette, tap card, alert button)kohami.agent.events, état du tour de parole (turn-state, end-of-turn)
Étape 4, parser le premier event widget
Tous les events DataChannel sont des objets JSON portant un champ discriminant kind. Importez les types et schémas Zod depuis @kohami/shared pour valider la trame côté client.
import {
NewsResultEventSchema,
QuizQuestionEventSchema,
WeatherResultEventSchema,
type NewsResultEvent,
type QuizQuestionEvent,
type WeatherResultEvent,
} from "@kohami/shared";
type WidgetEvent = WeatherResultEvent | NewsResultEvent | QuizQuestionEvent;
function parseWidgetEvent(raw: string): WidgetEvent | null {
const json = JSON.parse(raw) as { kind?: string };
switch (json.kind) {
case "weather:result":
return WeatherResultEventSchema.parse(json);
case "news:result":
return NewsResultEventSchema.parse(json);
case "quiz:question":
return QuizQuestionEventSchema.parse(json);
default:
return null;
}
}Étape 5, déclencher un widget côté senior
Le pipeline simple consiste à demander un widget météo via l'API REST de simulation (POST /v1/dev/widgets/weather/trigger, dev only). Le back-end publie ensuite weather:result puis kohami.tool.open sur kohami.widget.data. La tablette reçoit le résultat dans le DataReceived handler.
await fetch(`${apiBaseUrl}/v1/dev/widgets/weather/trigger`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ city: "Paris" }),
});En production, c'est l'agent vocal qui décide de publier ces events après un appel d'outil (getCurrentWeather, getLatestNews, etc.). Le contrat de payload reste identique.
Aller plus loin
- Authentification, refresh, 2FA TOTP, rôles
- Environnements, URLs dev/staging/prod et secrets
- Realtime voice, pipeline voix temps réel
- Data channels, topics inbound, outbound, agent
- Events catalogue, schémas auto-générés
- Cascade alertes, INV-S01 et INV-S02
- Reference REST API, catalogue d'endpoints complet