[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-60d2a849-4b2f-4533-9938-2e7fed04dcd7":3,"$fK5e8CstB3tD7yPmbov7sH3_wgRewdYF8UhNBDKdBx0A":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"60d2a849-4b2f-4533-9938-2e7fed04dcd7","azure-web-pubsub-ts","使用WebSocket连接和发布\u002F订阅模式的实时消息传递。","cat_coding_devops","mod_coding","sickn33,coding","---\nname: azure-web-pubsub-ts\ndescription: \"Real-time messaging with WebSocket connections and pub\u002Fsub patterns.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# Azure Web PubSub SDKs for TypeScript\n\nReal-time messaging with WebSocket connections and pub\u002Fsub patterns.\n\n## Installation\n\n```bash\n# Server-side management\nnpm install @azure\u002Fweb-pubsub @azure\u002Fidentity\n\n# Client-side real-time messaging\nnpm install @azure\u002Fweb-pubsub-client\n\n# Express middleware for event handlers\nnpm install @azure\u002Fweb-pubsub-express\n```\n\n## Environment Variables\n\n```bash\nWEBPUBSUB_CONNECTION_STRING=Endpoint=https:\u002F\u002F\u003Cresource>.webpubsub.azure.com;AccessKey=\u003Ckey>;Version=1.0;\nWEBPUBSUB_ENDPOINT=https:\u002F\u002F\u003Cresource>.webpubsub.azure.com\n```\n\n## Server-Side: WebPubSubServiceClient\n\n### Authentication\n\n```typescript\nimport { WebPubSubServiceClient, AzureKeyCredential } from \"@azure\u002Fweb-pubsub\";\nimport { DefaultAzureCredential } from \"@azure\u002Fidentity\";\n\n\u002F\u002F Connection string\nconst client = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_CONNECTION_STRING!,\n  \"chat\"  \u002F\u002F hub name\n);\n\n\u002F\u002F DefaultAzureCredential (recommended)\nconst client2 = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_ENDPOINT!,\n  new DefaultAzureCredential(),\n  \"chat\"\n);\n\n\u002F\u002F AzureKeyCredential\nconst client3 = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_ENDPOINT!,\n  new AzureKeyCredential(\"\u003Caccess-key>\"),\n  \"chat\"\n);\n```\n\n### Generate Client Access Token\n\n```typescript\n\u002F\u002F Basic token\nconst token = await client.getClientAccessToken();\nconsole.log(token.url);  \u002F\u002F wss:\u002F\u002F...?access_token=...\n\n\u002F\u002F Token with user ID\nconst userToken = await client.getClientAccessToken({\n  userId: \"user123\",\n});\n\n\u002F\u002F Token with permissions\nconst permToken = await client.getClientAccessToken({\n  userId: \"user123\",\n  roles: [\n    \"webpubsub.joinLeaveGroup\",\n    \"webpubsub.sendToGroup\",\n    \"webpubsub.sendToGroup.chat-room\",  \u002F\u002F specific group\n  ],\n  groups: [\"chat-room\"],  \u002F\u002F auto-join on connect\n  expirationTimeInMinutes: 60,\n});\n```\n\n### Send Messages\n\n```typescript\n\u002F\u002F Broadcast to all connections in hub\nawait client.sendToAll({ message: \"Hello everyone!\" });\nawait client.sendToAll(\"Plain text\", { contentType: \"text\u002Fplain\" });\n\n\u002F\u002F Send to specific user (all their connections)\nawait client.sendToUser(\"user123\", { message: \"Hello!\" });\n\n\u002F\u002F Send to specific connection\nawait client.sendToConnection(\"connectionId\", { data: \"Direct message\" });\n\n\u002F\u002F Send with filter (OData syntax)\nawait client.sendToAll({ message: \"Filtered\" }, {\n  filter: \"userId ne 'admin'\",\n});\n```\n\n### Group Management\n\n```typescript\nconst group = client.group(\"chat-room\");\n\n\u002F\u002F Add user\u002Fconnection to group\nawait group.addUser(\"user123\");\nawait group.addConnection(\"connectionId\");\n\n\u002F\u002F Remove from group\nawait group.removeUser(\"user123\");\n\n\u002F\u002F Send to group\nawait group.sendToAll({ message: \"Group message\" });\n\n\u002F\u002F Close all connections in group\nawait group.closeAllConnections({ reason: \"Maintenance\" });\n```\n\n### Connection Management\n\n```typescript\n\u002F\u002F Check existence\nconst userExists = await client.userExists(\"user123\");\nconst connExists = await client.connectionExists(\"connectionId\");\n\n\u002F\u002F Close connections\nawait client.closeConnection(\"connectionId\", { reason: \"Kicked\" });\nawait client.closeUserConnections(\"user123\");\nawait client.closeAllConnections();\n\n\u002F\u002F Permissions\nawait client.grantPermission(\"connectionId\", \"sendToGroup\", { targetName: \"chat\" });\nawait client.revokePermission(\"connectionId\", \"sendToGroup\", { targetName: \"chat\" });\n```\n\n## Client-Side: WebPubSubClient\n\n### Connect\n\n```typescript\nimport { WebPubSubClient } from \"@azure\u002Fweb-pubsub-client\";\n\n\u002F\u002F Direct URL\nconst client = new WebPubSubClient(\"\u003Cclient-access-url>\");\n\n\u002F\u002F Dynamic URL from negotiate endpoint\nconst client2 = new WebPubSubClient({\n  getClientAccessUrl: async () => {\n    const response = await fetch(\"\u002Fnegotiate\");\n    const { url } = await response.json();\n    return url;\n  },\n});\n\n\u002F\u002F Register handlers BEFORE starting\nclient.on(\"connected\", (e) => {\n  console.log(`Connected: ${e.connectionId}`);\n});\n\nclient.on(\"group-message\", (e) => {\n  console.log(`${e.message.group}: ${e.message.data}`);\n});\n\nawait client.start();\n```\n\n### Send Messages\n\n```typescript\n\u002F\u002F Join group first\nawait client.joinGroup(\"chat-room\");\n\n\u002F\u002F Send to group\nawait client.sendToGroup(\"chat-room\", \"Hello!\", \"text\");\nawait client.sendToGroup(\"chat-room\", { type: \"message\", content: \"Hi\" }, \"json\");\n\n\u002F\u002F Send options\nawait client.sendToGroup(\"chat-room\", \"Hello\", \"text\", {\n  noEcho: true,        \u002F\u002F Don't echo back to sender\n  fireAndForget: true, \u002F\u002F Don't wait for ack\n});\n\n\u002F\u002F Send event to server\nawait client.sendEvent(\"userAction\", { action: \"typing\" }, \"json\");\n```\n\n### Event Handlers\n\n```typescript\n\u002F\u002F Connection lifecycle\nclient.on(\"connected\", (e) => {\n  console.log(`Connected: ${e.connectionId}, User: ${e.userId}`);\n});\n\nclient.on(\"disconnected\", (e) => {\n  console.log(`Disconnected: ${e.message}`);\n});\n\nclient.on(\"stopped\", () => {\n  console.log(\"Client stopped\");\n});\n\n\u002F\u002F Messages\nclient.on(\"group-message\", (e) => {\n  console.log(`[${e.message.group}] ${e.message.fromUserId}: ${e.message.data}`);\n});\n\nclient.on(\"server-message\", (e) => {\n  console.log(`Server: ${e.message.data}`);\n});\n\n\u002F\u002F Rejoin failure\nclient.on(\"rejoin-group-failed\", (e) => {\n  console.log(`Failed to rejoin ${e.group}: ${e.error}`);\n});\n```\n\n## Express Event Handler\n\n```typescript\nimport express from \"express\";\nimport { WebPubSubEventHandler } from \"@azure\u002Fweb-pubsub-express\";\n\nconst app = express();\n\nconst handler = new WebPubSubEventHandler(\"chat\", {\n  path: \"\u002Fapi\u002Fwebpubsub\u002Fhubs\u002Fchat\u002F\",\n  \n  \u002F\u002F Blocking: approve\u002Freject connection\n  handleConnect: (req, res) => {\n    if (!req.claims?.sub) {\n      res.fail(401, \"Authentication required\");\n      return;\n    }\n    res.success({\n      userId: req.claims.sub[0],\n      groups: [\"general\"],\n      roles: [\"webpubsub.sendToGroup\"],\n    });\n  },\n  \n  \u002F\u002F Blocking: handle custom events\n  handleUserEvent: (req, res) => {\n    console.log(`Event from ${req.context.userId}:`, req.data);\n    res.success(`Received: ${req.data}`, \"text\");\n  },\n  \n  \u002F\u002F Non-blocking\n  onConnected: (req) => {\n    console.log(`Client connected: ${req.context.connectionId}`);\n  },\n  \n  onDisconnected: (req) => {\n    console.log(`Client disconnected: ${req.context.connectionId}`);\n  },\n});\n\napp.use(handler.getMiddleware());\n\n\u002F\u002F Negotiate endpoint\napp.get(\"\u002Fnegotiate\", async (req, res) => {\n  const token = await serviceClient.getClientAccessToken({\n    userId: req.user?.id,\n  });\n  res.json({ url: token.url });\n});\n\napp.listen(8080);\n```\n\n## Key Types\n\n```typescript\n\u002F\u002F Server\nimport {\n  WebPubSubServiceClient,\n  WebPubSubGroup,\n  GenerateClientTokenOptions,\n  HubSendToAllOptions,\n} from \"@azure\u002Fweb-pubsub\";\n\n\u002F\u002F Client\nimport {\n  WebPubSubClient,\n  WebPubSubClientOptions,\n  OnConnectedArgs,\n  OnGroupDataMessageArgs,\n} from \"@azure\u002Fweb-pubsub-client\";\n\n\u002F\u002F Express\nimport {\n  WebPubSubEventHandler,\n  ConnectRequest,\n  UserEventRequest,\n  ConnectResponseHandler,\n} from \"@azure\u002Fweb-pubsub-express\";\n```\n\n## Best Practices\n\n1. **Use Entra ID auth** - `DefaultAzureCredential` for production\n2. **Register handlers before start** - Don't miss initial events\n3. **Use groups for channels** - Organize messages by topic\u002Froom\n4. **Handle reconnection** - Client auto-reconnects by default\n5. **Validate in handleConnect** - Reject unauthorized connections early\n6. **Use noEcho** - Prevent message echo back to sender when needed\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,242,1983,"2026-05-16 13:08:01",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"DevOps","devops","mdi-cog-outline","CI\u002FCD、容器化、部署运维",3,162,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"b5c9e9f4-0984-423e-8c31-469721a6d903","1.0.0","azure-web-pubsub-ts.zip",2751,"uploads\u002Fskills\u002F60d2a849-4b2f-4533-9938-2e7fed04dcd7\u002Fazure-web-pubsub-ts.zip","6e9d9ff87101eb0d73e28f62d43f0afe97bd409f0775094e108b975c0637d5fd","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":7899}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]