{"id":"822ee32a-4e30-443e-834f-912cfbb5478f","shortId":"3DChPc","kind":"skill","title":"azure-web-pubsub-ts","tagline":"Real-time messaging with WebSocket connections and pub/sub patterns.","description":"# Azure Web PubSub SDKs for TypeScript\n\nReal-time messaging with WebSocket connections and pub/sub patterns.\n\n## Installation\n\n```bash\n# Server-side management\nnpm install @azure/web-pubsub @azure/identity\n\n# Client-side real-time messaging\nnpm install @azure/web-pubsub-client\n\n# Express middleware for event handlers\nnpm install @azure/web-pubsub-express\n```\n\n## Environment Variables\n\n```bash\nWEBPUBSUB_CONNECTION_STRING=Endpoint=https://<resource>.webpubsub.azure.com;AccessKey=<key>;Version=1.0;\nWEBPUBSUB_ENDPOINT=https://<resource>.webpubsub.azure.com\n```\n\n## Server-Side: WebPubSubServiceClient\n\n### Authentication\n\n```typescript\nimport { WebPubSubServiceClient, AzureKeyCredential } from \"@azure/web-pubsub\";\nimport { DefaultAzureCredential } from \"@azure/identity\";\n\n// Connection string\nconst client = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_CONNECTION_STRING!,\n  \"chat\"  // hub name\n);\n\n// DefaultAzureCredential (recommended)\nconst client2 = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_ENDPOINT!,\n  new DefaultAzureCredential(),\n  \"chat\"\n);\n\n// AzureKeyCredential\nconst client3 = new WebPubSubServiceClient(\n  process.env.WEBPUBSUB_ENDPOINT!,\n  new AzureKeyCredential(\"<access-key>\"),\n  \"chat\"\n);\n```\n\n### Generate Client Access Token\n\n```typescript\n// Basic token\nconst token = await client.getClientAccessToken();\nconsole.log(token.url);  // wss://...?access_token=...\n\n// Token with user ID\nconst userToken = await client.getClientAccessToken({\n  userId: \"user123\",\n});\n\n// Token with permissions\nconst permToken = await client.getClientAccessToken({\n  userId: \"user123\",\n  roles: [\n    \"webpubsub.joinLeaveGroup\",\n    \"webpubsub.sendToGroup\",\n    \"webpubsub.sendToGroup.chat-room\",  // specific group\n  ],\n  groups: [\"chat-room\"],  // auto-join on connect\n  expirationTimeInMinutes: 60,\n});\n```\n\n### Send Messages\n\n```typescript\n// Broadcast to all connections in hub\nawait client.sendToAll({ message: \"Hello everyone!\" });\nawait client.sendToAll(\"Plain text\", { contentType: \"text/plain\" });\n\n// Send to specific user (all their connections)\nawait client.sendToUser(\"user123\", { message: \"Hello!\" });\n\n// Send to specific connection\nawait client.sendToConnection(\"connectionId\", { data: \"Direct message\" });\n\n// 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// Add user/connection to group\nawait group.addUser(\"user123\");\nawait group.addConnection(\"connectionId\");\n\n// Remove from group\nawait group.removeUser(\"user123\");\n\n// Send to group\nawait group.sendToAll({ message: \"Group message\" });\n\n// Close all connections in group\nawait group.closeAllConnections({ reason: \"Maintenance\" });\n```\n\n### Connection Management\n\n```typescript\n// Check existence\nconst userExists = await client.userExists(\"user123\");\nconst connExists = await client.connectionExists(\"connectionId\");\n\n// Close connections\nawait client.closeConnection(\"connectionId\", { reason: \"Kicked\" });\nawait client.closeUserConnections(\"user123\");\nawait client.closeAllConnections();\n\n// 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/web-pubsub-client\";\n\n// Direct URL\nconst client = new WebPubSubClient(\"<client-access-url>\");\n\n// Dynamic URL from negotiate endpoint\nconst client2 = new WebPubSubClient({\n  getClientAccessUrl: async () => {\n    const response = await fetch(\"/negotiate\");\n    const { url } = await response.json();\n    return url;\n  },\n});\n\n// 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// Join group first\nawait client.joinGroup(\"chat-room\");\n\n// Send to group\nawait client.sendToGroup(\"chat-room\", \"Hello!\", \"text\");\nawait client.sendToGroup(\"chat-room\", { type: \"message\", content: \"Hi\" }, \"json\");\n\n// Send options\nawait client.sendToGroup(\"chat-room\", \"Hello\", \"text\", {\n  noEcho: true,        // Don't echo back to sender\n  fireAndForget: true, // Don't wait for ack\n});\n\n// Send event to server\nawait client.sendEvent(\"userAction\", { action: \"typing\" }, \"json\");\n```\n\n### Event Handlers\n\n```typescript\n// 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// 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// 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/web-pubsub-express\";\n\nconst app = express();\n\nconst handler = new WebPubSubEventHandler(\"chat\", {\n  path: \"/api/webpubsub/hubs/chat/\",\n  \n  // Blocking: approve/reject 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  // 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  // 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// Negotiate endpoint\napp.get(\"/negotiate\", 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// Server\nimport {\n  WebPubSubServiceClient,\n  WebPubSubGroup,\n  GenerateClientTokenOptions,\n  HubSendToAllOptions,\n} from \"@azure/web-pubsub\";\n\n// Client\nimport {\n  WebPubSubClient,\n  WebPubSubClientOptions,\n  OnConnectedArgs,\n  OnGroupDataMessageArgs,\n} from \"@azure/web-pubsub-client\";\n\n// Express\nimport {\n  WebPubSubEventHandler,\n  ConnectRequest,\n  UserEventRequest,\n  ConnectResponseHandler,\n} from \"@azure/web-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/room\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.","tags":["azure","web","pubsub","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding"],"capabilities":["skill","source-sickn33","skill-azure-web-pubsub-ts","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/azure-web-pubsub-ts","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34928 github stars · SKILL.md body (7,722 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T18:50:34.809Z","embedding":null,"createdAt":"2026-04-18T21:33:17.251Z","updatedAt":"2026-04-24T18:50:34.809Z","lastSeenAt":"2026-04-24T18:50:34.809Z","tsv":"'/api/webpubsub/hubs/chat':511 '/negotiate':342,570 '0':529 '1':615 '1.0':70 '2':623 '3':633 '4':642 '401':522 '5':651 '6':659 '60':173 '8080':585 'access':124,135 'accesskey':68 'ack':423 'action':431,682 'add':238 'admin':228 'app':503 'app.get':569 'app.listen':584 'app.use':565 'applic':676 'approve/reject':513 'ask':720 'async':337,571 'auth':619 'authent':78,523 'auto':168,647 'auto-join':167 'auto-reconnect':646 'await':131,143,152,183,188,201,210,221,242,245,251,257,267,278,283,288,293,296,299,305,340,345,367,375,383,390,402,428,576 'azur':2,16 'azure-web-pubsub-t':1 'azure/identity':41,88 'azure/web-pubsub':40,84,596 'azure/web-pubsub-client':51,320,604 'azure/web-pubsub-express':59,501,612 'azurekeycredenti':82,112,120 'back':414,665 'bash':33,62 'basic':127 'best':613 'block':512,534,552 'boundari':728 'broadcast':177 'channel':637 'chat':98,111,121,165,236,304,310,378,386,393,405,509 'chat-room':164,235,377,385,392,404 'check':274 'clarif':722 'clear':695 'client':43,92,123,312,324,456,556,562,597,645 'client-sid':42,311 'client.closeallconnections':297 'client.closeconnection':289 'client.closeuserconnections':294 'client.connectionexists':284 'client.getclientaccesstoken':132,144,153 'client.grantpermission':300 'client.group':234 'client.joingroup':376 'client.on':353,359,439,447,453,459,468,478 'client.revokepermission':306 'client.sendevent':429 'client.sendtoall':184,189,222 'client.sendtoconnection':211 'client.sendtogroup':384,391,403 'client.sendtouser':202 'client.start':368 'client.userexists':279 'client2':104,333 'client3':114 'close':262,286 'connect':12,28,64,89,96,171,180,200,209,264,271,287,315,354,357,437,440,443,514,557,657 'connectionid':212,247,285,290,301,307 'connectrequest':608 'connectresponsehandl':610 'connexist':282 'console.log':133,356,364,442,450,455,464,473,484,541,555,561 'const':91,103,113,129,141,150,232,276,281,323,332,338,343,502,505,574 'content':397 'contenttyp':192 'criteria':731 'custom':536 'data':213 'default':650 'defaultazurecredenti':86,101,110,620 'describ':683,699 'direct':214,321 'disconnect':448,451,563 'dynam':327 'e':355,363,441,449,463,472,483 'e.connectionid':358,444 'e.error':489 'e.group':488 'e.message':452 'e.message.data':366,467,475 'e.message.fromuserid':466 'e.message.group':365,465 'e.userid':446 'earli':658 'echo':413,664 'endpoint':66,72,108,118,331,568 'entra':617 'environ':60,711 'environment-specif':710 'event':55,425,434,491,537,542,632 'everyon':187 'execut':678 'exist':275 'expert':716 'expirationtimeinminut':172 'express':52,490,495,497,504,605 'fail':482,485 'failur':477 'fetch':341 'filter':218,224,225 'fireandforget':417 'first':374 'general':531 'generat':122 'generateclienttokenopt':593 'getclientaccessurl':336 'group':162,163,229,233,241,250,256,260,266,361,373,382,461,481,530,635 'group-messag':360,460 'group.addconnection':246 'group.adduser':243 'group.closeallconnections':268 'group.removeuser':252 'group.sendtoall':258 'handl':535,643 'handleconnect':515,654 'handler':56,350,435,492,506,625 'handler.getmiddleware':566 'handleuserev':538 'hello':186,205,388,407 'hi':398 'hub':99,182 'hubsendtoallopt':594 'id':140,580,618 'import':80,85,317,494,498,590,598,606 'initi':631 'input':725 'instal':32,39,50,58 'join':169,372 'json':399,433 'key':586 'kick':292 'lifecycl':438 'limit':687 'mainten':270 'manag':37,230,272 'match':696 'messag':9,25,48,175,185,204,215,223,259,261,362,370,396,458,462,471,639,663 'middlewar':53 'miss':630,733 'name':100 'ne':227 'need':669 'negoti':330,567 'new':93,105,109,115,119,325,334,507 'noecho':409,661 'non':551 'non-block':550 'npm':38,49,57 'odata':219 'onconnect':553 'onconnectedarg':601 'ondisconnect':559 'ongroupdatamessagearg':602 'option':401 'organ':638 'output':705 'overview':686 'path':510 'pattern':15,31 'permiss':149,298,726 'permtoken':151 'plain':190 'practic':614 'prevent':662 'process.env.webpubsub':95,107,117 'product':622 'pub/sub':14,30 'pubsub':4,18 'real':7,23,46 'real-tim':6,22,45 'reason':269,291 'receiv':547 'recommend':102 'reconnect':644,648 'regist':349,624 'reject':655 'rejoin':476,480,487 'rejoin-group-fail':479 'remov':248 'req':516,539,554,560,572 'req.claims':519 'req.claims.sub':528 'req.context.connectionid':558,564 'req.context.userid':544 'req.data':545,548 'req.user':579 'requir':524,724 'res':517,540,573 'res.fail':521 'res.json':581 'res.success':526,546 'respons':339 'response.json':346 'return':347,525 'review':717 'role':156,532 'room':160,166,237,379,387,394,406 'safeti':727 'scope':698 'sdks':19 'send':174,194,206,216,254,369,380,400,424 'sender':416,667 'sendtogroup':302,308 'server':35,75,427,470,474,589 'server-messag':469 'server-sid':34,74 'serviceclient.getclientaccesstoken':577 'side':36,44,76,313 'skill':674,690 'skill-azure-web-pubsub-ts' 'source-sickn33' 'specif':161,196,208,712 'start':352,627 'stop':454,457,718 'string':65,90,97 'sub':520 'substitut':708 'success':730 'syntax':220 'targetnam':303,309 'task':694 'test':714 'text':191,389,408,549 'text/plain':193 'time':8,24,47 'token':125,128,130,136,137,147,575 'token.url':134,583 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'topic/room':641 'treat':703 'true':410,418 'ts':5 'type':395,432,587 'typescript':21,79,126,176,231,273,316,371,436,493,588 'unauthor':656 'url':322,328,344,348,582 'use':616,634,660,672,688 'user':139,197,445 'user/connection':239 'user123':146,155,203,244,253,280,295 'useract':430 'usereventrequest':609 'userexist':277 'userid':145,154,226,527,578 'usertoken':142 'valid':652,713 'variabl':61 'version':69 'wait':421 'web':3,17 'webpubsub':63,71 'webpubsub.azure.com':67,73 'webpubsub.joinleavegroup':157 'webpubsub.sendtogroup':158,533 'webpubsub.sendtogroup.chat':159 'webpubsubcli':314,318,326,335,599 'webpubsubclientopt':600 'webpubsubeventhandl':499,508,607 'webpubsubgroup':592 'webpubsubservicecli':77,81,94,106,116,591 'websocket':11,27 'workflow':680","prices":[{"id":"4595c96b-e22c-48c1-9cef-4cab67bb9ad4","listingId":"822ee32a-4e30-443e-834f-912cfbb5478f","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:33:17.251Z"}],"sources":[{"listingId":"822ee32a-4e30-443e-834f-912cfbb5478f","source":"github","sourceId":"sickn33/antigravity-awesome-skills/azure-web-pubsub-ts","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/azure-web-pubsub-ts","isPrimary":false,"firstSeenAt":"2026-04-18T21:33:17.251Z","lastSeenAt":"2026-04-24T18:50:34.809Z"}],"details":{"listingId":"822ee32a-4e30-443e-834f-912cfbb5478f","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"azure-web-pubsub-ts","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34928,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T06:41:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"9d399c8331b5deb8e61e8aada85b257311a72dda","skill_md_path":"skills/azure-web-pubsub-ts/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/azure-web-pubsub-ts"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"azure-web-pubsub-ts","description":"Real-time messaging with WebSocket connections and pub/sub patterns."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/azure-web-pubsub-ts"},"updatedAt":"2026-04-24T18:50:34.809Z"}}