azure-web-pubsub-ts

Créez des applications de messagerie en temps réel avec les SDK Azure Web PubSub pour JavaScript (`@azure/web-pubsub`, `@azure/web-pubsub-client`). À utiliser pour implémenter des fonctionnalités temps réel basées sur WebSocket, de la messagerie pub/sub, du chat de groupe ou des notifications en direct.

npx skills add https://github.com/microsoft/skills --skill azure-web-pubsub-ts

Azure Web PubSub SDKs pour TypeScript

Messagerie en temps réel avec connexions WebSocket et patterns pub/sub.

Installation

# Gestion côté serveur
npm install @azure/web-pubsub @azure/identity

# Messagerie en temps réel côté client
npm install @azure/web-pubsub-client

# Middleware Express pour les gestionnaires d'événements
npm install @azure/web-pubsub-express

Variables d'environnement

WEBPUBSUB_CONNECTION_STRING=Endpoint=https://<resource>.webpubsub.azure.com;AccessKey=<key>;Version=1.0;
WEBPUBSUB_ENDPOINT=https://<resource>.webpubsub.azure.com
AZURE_TOKEN_CREDENTIALS=prod # Requis uniquement si DefaultAzureCredential est utilisée en production

Côté serveur : WebPubSubServiceClient

Authentification

import { WebPubSubServiceClient, AzureKeyCredential } from "@azure/web-pubsub";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";

// Dev local : DefaultAzureCredential. Production : définir AZURE_TOKEN_CREDENTIALS=prod ou AZURE_TOKEN_CREDENTIALS=<specific_credential>
const credential = new DefaultAzureCredential({requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"]});
// Ou utiliser une credential spécifique directement en production :
// Voir https://learn.microsoft.com/javascript/api/overview/azure/identity-readme?view=azure-node-latest#credential-classes
// const credential = new ManagedIdentityCredential();

// Chaîne de connexion
const client = new WebPubSubServiceClient(
  process.env.WEBPUBSUB_CONNECTION_STRING!,
  "chat"  // nom du hub
);

// Microsoft Entra Token Credential (recommandé)
const client2 = new WebPubSubServiceClient(
  process.env.WEBPUBSUB_ENDPOINT!,
  credential,
  "chat"
);

// AzureKeyCredential
const client3 = new WebPubSubServiceClient(
  process.env.WEBPUBSUB_ENDPOINT!,
  new AzureKeyCredential("<access-key>"),
  "chat"
);

Générer un token d'accès client

// Token basique
const token = await client.getClientAccessToken();
console.log(token.url);  // wss://...?access_token=...

// Token avec ID utilisateur
const userToken = await client.getClientAccessToken({
  userId: "user123",
});

// Token avec permissions
const permToken = await client.getClientAccessToken({
  userId: "user123",
  roles: [
    "webpubsub.joinLeaveGroup",
    "webpubsub.sendToGroup",
    "webpubsub.sendToGroup.chat-room",  // groupe spécifique
  ],
  groups: ["chat-room"],  // rejoindre automatiquement à la connexion
  expirationTimeInMinutes: 60,
});

Envoyer des messages

// Diffuser à toutes les connexions du hub
await client.sendToAll({ message: "Hello everyone!" });
await client.sendToAll("Plain text", { contentType: "text/plain" });

// Envoyer à un utilisateur spécifique (toutes ses connexions)
await client.sendToUser("user123", { message: "Hello!" });

// Envoyer à une connexion spécifique
await client.sendToConnection("connectionId", { data: "Direct message" });

// Envoyer avec filtre (syntaxe OData)
await client.sendToAll({ message: "Filtered" }, {
  filter: "userId ne 'admin'",
});

Gestion des groupes

const group = client.group("chat-room");

// Ajouter un utilisateur/une connexion au groupe
await group.addUser("user123");
await group.addConnection("connectionId");

// Retirer du groupe
await group.removeUser("user123");

// Envoyer au groupe
await group.sendToAll({ message: "Group message" });

// Fermer toutes les connexions du groupe
await group.closeAllConnections({ reason: "Maintenance" });

Gestion des connexions

// Vérifier l'existence
const userExists = await client.userExists("user123");
const connExists = await client.connectionExists("connectionId");

// Fermer les connexions
await client.closeConnection("connectionId", { reason: "Kicked" });
await client.closeUserConnections("user123");
await client.closeAllConnections();

// Permissions
await client.grantPermission("connectionId", "sendToGroup", { targetName: "chat" });
await client.revokePermission("connectionId", "sendToGroup", { targetName: "chat" });

Côté client : WebPubSubClient

Connexion

import { WebPubSubClient } from "@azure/web-pubsub-client";

// URL directe
const client = new WebPubSubClient("<client-access-url>");

// URL dynamique depuis l'endpoint de négociation
const client2 = new WebPubSubClient({
  getClientAccessUrl: async () => {
    const response = await fetch("/negotiate");
    const { url } = await response.json();
    return url;
  },
});

// Enregistrer les gestionnaires AVANT de démarrer
client.on("connected", (e) => {
  console.log(`Connected: ${e.connectionId}`);
});

client.on("group-message", (e) => {
  console.log(`${e.message.group}: ${e.message.data}`);
});

await client.start();

Envoyer des messages

// Rejoindre d'abord un groupe
await client.joinGroup("chat-room");

// Envoyer au groupe
await client.sendToGroup("chat-room", "Hello!", "text");
await client.sendToGroup("chat-room", { type: "message", content: "Hi" }, "json");

// Options d'envoi
await client.sendToGroup("chat-room", "Hello", "text", {
  noEcho: true,        // Ne pas renvoyer à l'expéditeur
  fireAndForget: true, // Ne pas attendre l'ack
});

// Envoyer un événement au serveur
await client.sendEvent("userAction", { action: "typing" }, "json");

Gestionnaires d'événements

// Cycle de vie de la connexion
client.on("connected", (e) => {
  console.log(`Connected: ${e.connectionId}, User: ${e.userId}`);
});

client.on("disconnected", (e) => {
  console.log(`Disconnected: ${e.message}`);
});

client.on("stopped", () => {
  console.log("Client stopped");
});

// Messages
client.on("group-message", (e) => {
  console.log(`[${e.message.group}] ${e.message.fromUserId}: ${e.message.data}`);
});

client.on("server-message", (e) => {
  console.log(`Server: ${e.message.data}`);
});

// Échec de rejoindre
client.on("rejoin-group-failed", (e) => {
  console.log(`Failed to rejoin ${e.group}: ${e.error}`);
});

Gestionnaire d'événements Express

import express from "express";
import { WebPubSubEventHandler } from "@azure/web-pubsub-express";

const app = express();

const handler = new WebPubSubEventHandler("chat", {
  path: "/api/webpubsub/hubs/chat/",

  // Bloquant : approuver/rejeter la connexion
  handleConnect: (req, res) => {
    if (!req.claims?.sub) {
      res.fail(401, "Authentication required");
      return;
    }
    res.success({
      userId: req.claims.sub[0],
      groups: ["general"],
      roles: ["webpubsub.sendToGroup"],
    });
  },

  // Bloquant : gérer les événements personnalisés
  handleUserEvent: (req, res) => {
    console.log(`Event from ${req.context.userId}:`, req.data);
    res.success(`Received: ${req.data}`, "text");
  },

  // Non-bloquant
  onConnected: (req) => {
    console.log(`Client connected: ${req.context.connectionId}`);
  },

  onDisconnected: (req) => {
    console.log(`Client disconnected: ${req.context.connectionId}`);
  },
});

app.use(handler.getMiddleware());

// Endpoint de négociation
app.get("/negotiate", async (req, res) => {
  const token = await serviceClient.getClientAccessToken({
    userId: req.user?.id,
  });
  res.json({ url: token.url });
});

app.listen(8080);

Types clés

// Serveur
import {
  WebPubSubServiceClient,
  WebPubSubGroup,
  GenerateClientTokenOptions,
  HubSendToAllOptions,
} from "@azure/web-pubsub";

// Client
import {
  WebPubSubClient,
  WebPubSubClientOptions,
  OnConnectedArgs,
  OnGroupDataMessageArgs,
} from "@azure/web-pubsub-client";

// Express
import {
  WebPubSubEventHandler,
  ConnectRequest,
  UserEventRequest,
  ConnectResponseHandler,
} from "@azure/web-pubsub-express";

Bonnes pratiques

  1. Utiliser Microsoft Entra Token Credential - Utiliser DefaultAzureCredential pour le dev local ; utiliser ManagedIdentityCredential ou WorkloadIdentityCredential en production
  2. Enregistrer les gestionnaires avant le démarrage - Ne pas manquer les événements initiaux
  3. Utiliser des groupes pour les canaux - Organiser les messages par sujet/room
  4. Gérer la reconnexion - Le client se reconnecte automatiquement par défaut
  5. Valider dans handleConnect - Rejeter les connexions non autorisées dès le début
  6. Utiliser noEcho - Empêcher l'écho des messages vers l'expéditeur quand nécessaire

Skills similaires