Azure Web PubSub TypeScript SDK
Azure Web PubSub TypeScript SDK
基于 WebSocket 连接和发布/订阅模式的实时消息传递。
安装
# 服务端管理
npm install @azure/web-pubsub @azure/identity
客户端实时消息传递
npm install @azure/web-pubsub-client
用于事件处理程序的 Express 中间件
npm install @azure/web-pubsub-express环境变量
WEBPUBSUB_CONNECTION_STRING=Endpoint=https://<resource>.webpubsub.azure.com;AccessKey=<key>;Version=1.0;
WEBPUBSUB_ENDPOINT=https://<resource>.webpubsub.azure.com服务端:WebPubSubServiceClient
身份验证
import { WebPubSubServiceClient, AzureKeyCredential } from "@azure/web-pubsub";
import { DefaultAzureCredential } from "@azure/identity";
// 使用连接字符串
const client = new WebPubSubServiceClient(
process.env.WEBPUBSUB_CONNECTION_STRING!,
"chat" // hub 名称
);
// 使用 DefaultAzureCredential (推荐)
const client2 = new WebPubSubServiceClient(
process.env.WEBPUBSUB_ENDPOINT!,
new DefaultAzureCredential(),
"chat"
);
// 使用 AzureKeyCredential
const client3 = new WebPubSubServiceClient(
process.env.WEBPUBSUB_ENDPOINT!,
new AzureKeyCredential("<access-key>"),
"chat"
);
生成客户端访问令牌
// 基础令牌
const token = await client.getClientAccessToken();
console.log(token.url); // wss://...?access_token=...
// 包含用户 ID 的令牌
const userToken = await client.getClientAccessToken({
userId: "user123",
});
// 包含权限的令牌
const permToken = await client.getClientAccessToken({
userId: "user123",
roles: [
"webpubsub.joinLeaveGroup",
"webpubsub.sendToGroup",
"webpubsub.sendToGroup.chat-room", // 特定组
],
groups: ["chat-room"], // 连接时自动加入
expirationTimeInMinutes: 60,
});
发送消息
// 广播给 hub 中的所有连接
await client.sendToAll({ message: "Hello everyone!" });
await client.sendToAll("Plain text", { contentType: "text/plain" });
// 发送给特定用户(其所有连接)
await client.sendToUser("user123", { message: "Hello!" });
// 发送给特定连接
await client.sendToConnection("connectionId", { data: "Direct message" });
// 使用过滤器发送 (OData 语法)
await client.sendToAll({ message: "Filtered" }, {
filter: "userId ne 'admin'",
});
组管理
const group = client.group("chat-room");
// 将用户/连接添加到组
await group.addUser("user123");
await group.addConnection("connectionId");
// 从组中移除
await group.removeUser("user123");
// 发送给组内所有成员
await group.sendToAll({ message: "Group message" });
// 关闭组内所有连接
await group.closeAllConnections({ reason: "Maintenance" });
连接管理
// 检查是否存在
const userExists = await client.userExists("user123");
const connExists = await client.connectionExists("connectionId");
// 关闭连接
await client.closeConnection("connectionId", { reason: "Kicked" });
await client.closeUserConnections("user123");
await client.closeAllConnections();
// 权限管理
await client.grantPermission("connectionId", "sendToGroup", { targetName: "chat" });
await client.revokePermission("connectionId", "sendToGroup", { targetName: "chat" })
客户端:WebPubSubClient
连接
import { WebPubSubClient } from "@azure/web-pubsub-client";
// 直接使用 URL
const client = new WebPubSubClient("<client-access-url>");
// 通过 negotiate 接口动态获取 URL
const client2 = new WebPubSubClient({
getClientAccessUrl: async () => {
const response = await fetch("/negotiate");
const { url } = await response.json();
return url;
},
});
// 在启动前注册处理器
client.on("connected", (e) => {
console.log(已连接: ${e.connectionId});
});
client.on("group-message", (e) => {
console.log(${e.message.group}: ${e.message.data});
});
await client.start();
发送消息
// 先加入组
await client.joinGroup("chat-room");
// 发送到组
await client.sendToGroup("chat-room", "Hello!", "text");
await client.sendToGroup("chat-room", { type: "message", content: "Hi" }, "json");
// 发送选项
await client.sendToGroup("chat-room", "Hello", "text", {
noEcho: true, // 不回显给发送者
fireAndForget: true, // 不等待确认响应 (ack)
});
// 向服务器发送事件
await client.sendEvent("userAction", { action: "typing" }, "json");
事件处理器
// 连接生命周期
client.on("connected", (e) => {
console.log(已连接: ${e.connectionId}, 用户: ${e.userId});
});
client.on("disconnected", (e) => {
console.log(已断开: ${e.message});
});
client.on("stopped", () => {
console.log("客户端已停止");
});
// 消息处理
client.on("group-message", (e) => {
console.log([${e.message.group}] ${e.message.fromUserId}: ${e.message.data});
});
client.on("server-message", (e) => {
console.log(服务器消息: ${e.message.data});
});
// 重新加入组失败
client.on("rejoin-group-failed", (e) => {
console.log(重新加入 ${e.group} 失败: ${e.error});
});
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/",
// 阻塞式:批准/拒绝连接
handleConnect: (req, res) => {
if (!req.claims?.sub) {
res.fail(401, "需要身份验证");
return;
}
res.success({
userId: req.claims.sub[0],
groups: ["general"],
roles: ["webpubsub.sendToGroup"],
});
},
// 阻塞式:处理自定义事件
handleUserEvent: (req, res) => {
console.log(来自 ${req.context.userId} 的事件:, req.data);
res.success(已收到: ${req.data}, "text");
},
// 非阻塞式
onConnected: (req) => {
console.log(客户端已连接: ${req.context.connectionId});
},
onDisconnected: (req) => {
console.log(客户端已断开: ${req.context.connectionId});
},
});
app.use(handler.getMiddleware());
// Negotiate 接口
app.get("/negotiate", async (req, res) => {
const token = await serviceClient.getClientAccessToken({
userId: req.user?.id,
});
res.json({ url: token.url });
});
app.listen(8080);
关键类型
// 服务端
import {
WebPubSubServiceClient,
WebPubSubGroup,
GenerateClientTokenOptions,
HubSendToAllOptions,
} from "@azure/web-pubsub";
// 客户端
import {
WebPubSubClient,
WebPubSubClientOptions,
OnConnectedArgs,
OnGroupDataMessageArgs,
} from "@azure/web-pubsub-client";
// Express
import {
WebPubSubEventHandler,
ConnectRequest,
UserEventRequest,
ConnectR
esponseHandler,
} from "@azure/web-pubsub-express";最佳实践
1. 使用 Entra ID 认证 - 生产环境建议使用 DefaultAzureCredential
2. 在启动前注册处理器 - 避免遗漏初始事件
3. 使用组(Groups)管理频道 - 按主题或房间组织消息
4. 处理重连 - 客户端默认支持自动重连
5. 在 handleConnect 中进行验证 - 尽早拒绝未经授权的连接
6. 使用 noEcho - 根据需要防止消息回显给发送者
适用场景
本技能适用于执行概览中所描述的工作流或操作。局限性
- 仅在任务与上述范围明确匹配时使用此技能。
- 不要将输出结果视为针对特定环境的验证、测试或专家评审的替代方案。
- 如果缺少必要的输入、权限、安全边界或成功标准,请停止操作并请求澄清。