[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CXT0D0M0Q4T2N7C2RX7DSN":3,"skill-same:01M6CXT0D0M0Q4T2N7C2RX7DSN":172},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":24,"linkOnly":25,"markdownContent":26,"markdownBody":27,"frontMatter":28,"refName":31,"commitSha":32,"skillDirectory":5,"forksCount":33,"repositoryHtmlUrl":34,"repositoryHomepageUrl":6,"ownerHtmlUrl":35,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":36,"install":37,"resources":50},"01M6CXT0D0M0Q4T2N7C2RX7DSN","websockets",null,"WebSocket implementation and best practices","ffsshhttiikk\u002Fopencode-agents-skills","ffsshhttiikk","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F226346827?v=4","01M6CWD9RR3Y7VRQX3MXYC8FYH","01M6CWD9WVPYAZW0J82G1VKGHY",3,"MIT","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwebsockets\u002FSKILL.md",[],[5,18,19,20,21,22,23],"best practices","websocket","real-time","opencode","implementation","time","2026-11-10 10:12:30",false,"---\nname: websockets\ndescription: WebSocket implementation and best practices\nlicense: MIT\ncompatibility: opencode\nmetadata:\n  audience: developers\n  category: real-time\n---\n## What I do\n- Implement WebSocket connections\n- Handle real-time bidirectional communication\n- Manage connection lifecycle\n- Implement authentication\n- Handle reconnection\n- Message framing and protocols\n- Scale WebSocket connections\n- Monitor and debug\n\n## When to use me\nWhen implementing real-time features with WebSockets.\n\n## WebSocket Architecture\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Client                                    │\n│  ┌─────────┐    ┌──────────┐    ┌─────────┐                    │\n│  │Socket.io│◄──►│Reconnect │◄──►│Protocol │                    │\n│  │ Client  │    │ Logic    │    │Parser   │                    │\n│  └─────────┘    └──────────┘    └─────────┘                    │\n└────────────────────────────┬────────────────────────────────────┘\n                             │ WebSocket\n┌────────────────────────────▼────────────────────────────────────┐\n│                      Load Balancer                              │\n│                    (Sticky Sessions)                           │\n└────────────────────────────┬────────────────────────────────────┘\n                             │\n         ┌───────────────────┼───────────────────┐\n         ▼                   ▼                   ▼\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│ WebSocket   │     │ WebSocket   │     │ WebSocket   │\n│ Server 1    │     │ Server 2    │     │ Server 3    │\n└─────────────┘     └─────────────┘     └─────────────┘\n         │                   │                   │\n         └───────────────────┼───────────────────┘\n                             │\n                    ┌────────▼────────┐\n                    │ Message Broker  │\n                    │ (Redis Pub\u002FSub) │\n                    └─────────────────┘\n```\n\n## Python WebSocket Server\n```python\nimport asyncio\nimport json\nfrom typing import Dict, Set\nfrom dataclasses import dataclass, field\nfrom enum import Enum\n\n\nclass MessageType(Enum):\n    # Client → Server\n    SUBSCRIBE = \"subscribe\"\n    UNSUBSCRIBE = \"unsubscribe\"\n    SEND_MESSAGE = \"send_message\"\n    \n    # Server → Client\n    WELCOME = \"welcome\"\n    MESSAGE = \"message\"\n    ERROR = \"error\"\n    SUBSCRIBED = \"subscribed\"\n    UNSUBSCRIBED = \"unsubscribed\"\n\n\n@dataclass\nclass Client:\n    id: str\n    websocket: asyncio.WebSocketConnection\n    subscriptions: Set[str] = field(default_factory=set)\n\n\nclass WebSocketServer:\n    \"\"\"WebSocket server with subscriptions and broadcasting.\"\"\"\n    \n    def __init__(self) -> None:\n        self.clients: Dict[str, Client] = {}\n        self.subscriptions: Dict[str, Set[str]] = {}  # topic -> client IDs\n    \n    async def handle_client(\n        self,\n        websocket: asyncio.WebSocketConnection,\n        client_id: str\n    ) -> None:\n        \"\"\"Handle individual client connection.\"\"\"\n        client = Client(id=client_id, websocket=websocket)\n        self.clients[client_id] = client\n        \n        # Send welcome message\n        await self.send_message(\n            client,\n            MessageType.WELCOME.value,\n            {\"client_id\": client_id}\n        )\n        \n        try:\n            async for message in websocket:\n                await self.handle_message(client, message)\n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            print(f\"Error handling client {client_id}: {e}\")\n        finally:\n            await self.disconnect(client)\n    \n    async def handle_message(\n        self,\n        client: Client,\n        raw_message: str\n    ) -> None:\n        \"\"\"Process incoming message.\"\"\"\n        try:\n            message = json.loads(raw_message)\n            msg_type = message.get(\"type\")\n            payload = message.get(\"payload\", {})\n            \n            if msg_type == MessageType.SUBSCRIBE.value:\n                await self.subscribe(client, payload.get(\"topic\"))\n            elif msg_type == MessageType.UNSUBSCRIBE.value:\n                await self.unsubscribe(client, payload.get(\"topic\"))\n            elif msg_type == MessageType.SEND_MESSAGE.value:\n                await self.send_to_topic(\n                    payload.get(\"topic\"),\n                    payload.get(\"message\")\n                )\n            else:\n                await self.send_error(client, f\"Unknown message type: {msg_type}\")\n        \n        except json.JSONDecodeError:\n            await self.send_error(client, \"Invalid JSON\")\n    \n    async def subscribe(self, client: Client, topic: str) -> None:\n        \"\"\"Subscribe client to a topic.\"\"\"\n        if topic not in self.subscriptions:\n            self.subscriptions[topic] = set()\n        \n        self.subscriptions[topic].add(client.id)\n        client.subscriptions.add(topic)\n        \n        await self.send_message(\n            client,\n            MessageType.SUBSCRIBED.value,\n            {\"topic\": topic}\n        )\n    \n    async def unsubscribe(self, client: Client, topic: str) -> None:\n        \"\"\"Unsubscribe from a topic.\"\"\"\n        if topic in self.subscriptions:\n            self.subscriptions[topic].discard(client.id)\n        \n        client.subscriptions.discard(topic)\n        \n        await self.send_message(\n            client,\n            MessageType.UNSUBSCRIBED.value,\n            {\"topic\": topic}\n        )\n    \n    async def send_to_topic(\n        self,\n        topic: str,\n        message: dict\n    ) -> None:\n        \"\"\"Broadcast message to all subscribers of a topic.\"\"\"\n        if topic not in self.subscriptions:\n            return\n        \n        payload = {\n            \"type\": MessageType.MESSAGE.value,\n            \"payload\": {\n                \"topic\": topic,\n                \"message\": message,\n            }\n        }\n        \n        # Send to all subscribers\n        for client_id in self.subscriptions[topic]:\n            if client_id in self.clients:\n                await self.send_message_json(\n                    self.clients[client_id],\n                    payload\n                )\n    \n    async def send_message(\n        self,\n        client: Client,\n        msg_type: str,\n        payload: dict\n    ) -> None:\n        \"\"\"Send message to client.\"\"\"\n        await self.send_message_json(client, {\n            \"type\": msg_type,\n            \"payload\": payload,\n        })\n    \n    async def send_message_json(\n        self,\n        client: Client,\n        message: dict\n    ) -> None:\n        \"\"\"Send JSON message to client.\"\"\"\n        try:\n            await client.websocket.send(json.dumps(message))\n        except Exception as e:\n            print(f\"Error sending to {client.id}: {e}\")\n            await self.disconnect(client)\n    \n    async def send_error(self, client: Client, error: str) -> None:\n        \"\"\"Send error message to client.\"\"\"\n        await self.send_message(\n            client,\n            MessageType.ERROR.value,\n            {\"error\": error}\n        )\n    \n    async def disconnect(self, client: Client) -> None:\n        \"\"\"Handle client disconnection.\"\"\"\n        # Unsubscribe from all topics\n        for topic in list(client.subscriptions):\n            if topic in self.subscriptions:\n                self.subscriptions[topic].discard(client.id)\n        \n        # Remove from clients\n        self.clients.pop(client.id, None)\n        \n        # Close connection\n        try:\n            await client.websocket.close()\n        except Exception:\n            pass\n```\n\n## Socket.IO Implementation\n```typescript\n\u002F\u002F Server-side (Node.js)\nimport { Server } from 'socket.io';\n\n\nconst io = new Server(3000, {\n  cors: {\n    origin: '*',\n    methods: ['GET', 'POST'],\n  },\n  pingTimeout: 60000,\n  pingInterval: 25000,\n});\n\n\n\u002F\u002F Authentication middleware\nio.use((socket, next) => {\n  const token = socket.handshake.auth.token;\n  \n  if (!token) {\n    return next(new Error('Authentication required'));\n  }\n  \n  try {\n    const user = verifyToken(token);\n    socket.user = user;\n    next();\n  } catch (e) {\n    next(new Error('Invalid token'));\n  }\n});\n\n\nio.on('connection', (socket) => {\n  console.log(`User connected: ${socket.user.id}`);\n  \n  \u002F\u002F Join rooms\n  socket.on('join:room', (roomId) => {\n    socket.join(roomId);\n    socket.to(roomId).emit('user:joined', {\n      userId: socket.user.id,\n      roomId,\n    });\n  });\n  \n  \u002F\u002F Leave rooms\n  socket.on('leave:room', (roomId) => {\n    socket.leave(roomId);\n    socket.to(roomId).emit('user:left', {\n      userId: socket.user.id,\n      roomId,\n    });\n  });\n  \n  \u002F\u002F Handle events\n  socket.on('message:send', (data) => {\n    const { roomId, content } = data;\n    \n    const message = {\n      id: generateId(),\n      content,\n      userId: socket.user.id,\n      timestamp: new Date().toISOString(),\n    };\n    \n    \u002F\u002F Broadcast to room\n    io.to(roomId).emit('message:received', message);\n  });\n  \n  \u002F\u002F Typing indicators\n  socket.on('typing:start', (roomId) => {\n    socket.to(roomId).emit('user:typing', {\n      userId: socket.user.id,\n      isTyping: true,\n    });\n  });\n  \n  socket.on('typing:stop', (roomId) => {\n    socket.to(roomId).emit('user:typing', {\n      userId: socket.user.id,\n      isTyping: false,\n    });\n  });\n  \n  \u002F\u002F Handle disconnection\n  socket.on('disconnect', (reason) => {\n    console.log(`User disconnected: ${socket.user.id}, reason: ${reason}`);\n    \n    \u002F\u002F Notify rooms\n    socket.rooms.forEach((roomId) => {\n      if (roomId !== socket.id) {\n        socket.to(roomId).emit('user:left', {\n          userId: socket.user.id,\n          roomId,\n        });\n      }\n    });\n  });\n});\n```\n\n## Client Implementation\n```typescript\n\u002F\u002F Client-side (React)\nimport { useEffect, useRef, useState, useCallback } from 'react';\nimport io, { Socket } from 'socket.io-client';\n\n\ninterface UseSocketOptions {\n  url: string;\n  token: string;\n  autoConnect?: boolean;\n}\n\n\nexport function useSocket({\n  url,\n  token,\n  autoConnect = true,\n}: UseSocketOptions) {\n  const socketRef = useRef\u003CSocket | null>(null);\n  const [isConnected, setIsConnected] = useState(false);\n  const [lastMessage, setLastMessage] = useState\u003Cany>(null);\n  \n  useEffect(() => {\n    socketRef.current = io(url, {\n      auth: { token },\n      transports: ['websocket'],\n      reconnection: true,\n      reconnectionAttempts: 5,\n      reconnectionDelay: 1000,\n    });\n    \n    socketRef.current.on('connect', () => {\n      setIsConnected(true);\n      console.log('Connected to WebSocket server');\n    });\n    \n    socketRef.current.on('disconnect', (reason) => {\n      setIsConnected(false);\n      console.log('Disconnected:', reason);\n    });\n    \n    socketRef.current.on('message:received', (message) => {\n      setLastMessage(message);\n    });\n    \n    if (autoConnect) {\n      socketRef.current.connect();\n    }\n    \n    return () => {\n      socketRef.current?.disconnect();\n    };\n  }, [url, token, autoConnect]);\n  \n  const joinRoom = useCallback((roomId: string) => {\n    socketRef.current?.emit('join:room', roomId);\n  }, []);\n  \n  const leaveRoom = useCallback((roomId: string) => {\n    socketRef.current?.emit('leave:room', roomId);\n  }, []);\n  \n  const sendMessage = useCallback((roomId: string, content: string) => {\n    socketRef.current?.emit('message:send', { roomId, content });\n  }, []);\n  \n  const on = useCallback((event: string, callback: (...args: any[]) => void) => {\n    socketRef.current?.on(event, callback);\n    return () => {\n      socketRef.current?.off(event, callback);\n    };\n  }, []);\n  \n  return {\n    isConnected,\n    lastMessage,\n    joinRoom,\n    leaveRoom,\n    sendMessage,\n    on,\n    socket: socketRef.current,\n  };\n}\n```\n\n## Scaling WebSockets\n```python\n# Using Redis Pub\u002FSub for horizontal scaling\nimport aioredis\nimport json\n\n\nclass RedisPubSubManager:\n    \"\"\"Redis Pub\u002FSub for multi-instance WebSocket scaling.\"\"\"\n    \n    def __init__(self, redis_url: str) -> None:\n        self.redis = aioredis.from_url(redis_url)\n        self.pubsub = self.redis.pubsub()\n        self.subscriptions: Dict[str, Set[str]] = {}\n        self.client_messages: asyncio.Queue = asyncio.Queue()\n    \n    async def subscribe(self, topic: str, server_id: str) -> None:\n        \"\"\"Subscribe to topic across all servers.\"\"\"\n        await self.redis.subscribe(topic)\n        \n        if topic not in self.subscriptions:\n            self.subscriptions[topic] = set()\n        \n        self.subscriptions[topic].add(server_id)\n    \n    async def publish(self, topic: str, message: dict) -> None:\n        \"\"\"Publish message to topic.\"\"\"\n        await self.redis.publish(\n            topic,\n            json.dumps({\n                'server_id': self.server_id,\n                'message': message,\n            })\n        )\n    \n    async def handle_messages(self) -> None:\n        \"\"\"Handle incoming pub\u002Fsub messages.\"\"\"\n        async for message in self.pubsub.listen():\n            if message['type'] == 'message':\n                data = json.loads(message['data'])\n                \n                # If from another server, relay to local clients\n                if data['server_id'] != self.server_id:\n                    await self.relay_to_local_clients(\n                        message['channel'],\n                        data['message']\n                    )\n```\n\n## Best Practices\n```\n1. Use secure WebSockets (wss:\u002F\u002F)\n   Always use TLS in production\n\n2. Authenticate connections\n   Validate tokens on connection\n\n3. Implement heartbeat\u002Fping\n   Detect dead connections quickly\n\n4. Handle reconnection\n   Exponential backoff with jitter\n\n5. Limit connection lifetime\n   Require re-authentication periodically\n\n6. Scale horizontally\n   Use Redis Pub\u002FSub for broadcasting\n\n7. Monitor connections\n   Track connection counts, message rates\n\n8. Set message size limits\n   Prevent memory exhaustion\n\n9. Handle backpressure\n   Don't overwhelm slow clients\n\n10. Graceful shutdown\n    Notify clients before disconnecting\n```\n","## What I do\n- Implement WebSocket connections\n- Handle real-time bidirectional communication\n- Manage connection lifecycle\n- Implement authentication\n- Handle reconnection\n- Message framing and protocols\n- Scale WebSocket connections\n- Monitor and debug\n\n## When to use me\nWhen implementing real-time features with WebSockets.\n\n## WebSocket Architecture\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Client                                    │\n│  ┌─────────┐    ┌──────────┐    ┌─────────┐                    │\n│  │Socket.io│◄──►│Reconnect │◄──►│Protocol │                    │\n│  │ Client  │    │ Logic    │    │Parser   │                    │\n│  └─────────┘    └──────────┘    └─────────┘                    │\n└────────────────────────────┬────────────────────────────────────┘\n                             │ WebSocket\n┌────────────────────────────▼────────────────────────────────────┐\n│                      Load Balancer                              │\n│                    (Sticky Sessions)                           │\n└────────────────────────────┬────────────────────────────────────┘\n                             │\n         ┌───────────────────┼───────────────────┐\n         ▼                   ▼                   ▼\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│ WebSocket   │     │ WebSocket   │     │ WebSocket   │\n│ Server 1    │     │ Server 2    │     │ Server 3    │\n└─────────────┘     └─────────────┘     └─────────────┘\n         │                   │                   │\n         └───────────────────┼───────────────────┘\n                             │\n                    ┌────────▼────────┐\n                    │ Message Broker  │\n                    │ (Redis Pub\u002FSub) │\n                    └─────────────────┘\n```\n\n## Python WebSocket Server\n```python\nimport asyncio\nimport json\nfrom typing import Dict, Set\nfrom dataclasses import dataclass, field\nfrom enum import Enum\n\n\nclass MessageType(Enum):\n    # Client → Server\n    SUBSCRIBE = \"subscribe\"\n    UNSUBSCRIBE = \"unsubscribe\"\n    SEND_MESSAGE = \"send_message\"\n    \n    # Server → Client\n    WELCOME = \"welcome\"\n    MESSAGE = \"message\"\n    ERROR = \"error\"\n    SUBSCRIBED = \"subscribed\"\n    UNSUBSCRIBED = \"unsubscribed\"\n\n\n@dataclass\nclass Client:\n    id: str\n    websocket: asyncio.WebSocketConnection\n    subscriptions: Set[str] = field(default_factory=set)\n\n\nclass WebSocketServer:\n    \"\"\"WebSocket server with subscriptions and broadcasting.\"\"\"\n    \n    def __init__(self) -> None:\n        self.clients: Dict[str, Client] = {}\n        self.subscriptions: Dict[str, Set[str]] = {}  # topic -> client IDs\n    \n    async def handle_client(\n        self,\n        websocket: asyncio.WebSocketConnection,\n        client_id: str\n    ) -> None:\n        \"\"\"Handle individual client connection.\"\"\"\n        client = Client(id=client_id, websocket=websocket)\n        self.clients[client_id] = client\n        \n        # Send welcome message\n        await self.send_message(\n            client,\n            MessageType.WELCOME.value,\n            {\"client_id\": client_id}\n        )\n        \n        try:\n            async for message in websocket:\n                await self.handle_message(client, message)\n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            print(f\"Error handling client {client_id}: {e}\")\n        finally:\n            await self.disconnect(client)\n    \n    async def handle_message(\n        self,\n        client: Client,\n        raw_message: str\n    ) -> None:\n        \"\"\"Process incoming message.\"\"\"\n        try:\n            message = json.loads(raw_message)\n            msg_type = message.get(\"type\")\n            payload = message.get(\"payload\", {})\n            \n            if msg_type == MessageType.SUBSCRIBE.value:\n                await self.subscribe(client, payload.get(\"topic\"))\n            elif msg_type == MessageType.UNSUBSCRIBE.value:\n                await self.unsubscribe(client, payload.get(\"topic\"))\n            elif msg_type == MessageType.SEND_MESSAGE.value:\n                await self.send_to_topic(\n                    payload.get(\"topic\"),\n                    payload.get(\"message\")\n                )\n            else:\n                await self.send_error(client, f\"Unknown message type: {msg_type}\")\n        \n        except json.JSONDecodeError:\n            await self.send_error(client, \"Invalid JSON\")\n    \n    async def subscribe(self, client: Client, topic: str) -> None:\n        \"\"\"Subscribe client to a topic.\"\"\"\n        if topic not in self.subscriptions:\n            self.subscriptions[topic] = set()\n        \n        self.subscriptions[topic].add(client.id)\n        client.subscriptions.add(topic)\n        \n        await self.send_message(\n            client,\n            MessageType.SUBSCRIBED.value,\n            {\"topic\": topic}\n        )\n    \n    async def unsubscribe(self, client: Client, topic: str) -> None:\n        \"\"\"Unsubscribe from a topic.\"\"\"\n        if topic in self.subscriptions:\n            self.subscriptions[topic].discard(client.id)\n        \n        client.subscriptions.discard(topic)\n        \n        await self.send_message(\n            client,\n            MessageType.UNSUBSCRIBED.value,\n            {\"topic\": topic}\n        )\n    \n    async def send_to_topic(\n        self,\n        topic: str,\n        message: dict\n    ) -> None:\n        \"\"\"Broadcast message to all subscribers of a topic.\"\"\"\n        if topic not in self.subscriptions:\n            return\n        \n        payload = {\n            \"type\": MessageType.MESSAGE.value,\n            \"payload\": {\n                \"topic\": topic,\n                \"message\": message,\n            }\n        }\n        \n        # Send to all subscribers\n        for client_id in self.subscriptions[topic]:\n            if client_id in self.clients:\n                await self.send_message_json(\n                    self.clients[client_id],\n                    payload\n                )\n    \n    async def send_message(\n        self,\n        client: Client,\n        msg_type: str,\n        payload: dict\n    ) -> None:\n        \"\"\"Send message to client.\"\"\"\n        await self.send_message_json(client, {\n            \"type\": msg_type,\n            \"payload\": payload,\n        })\n    \n    async def send_message_json(\n        self,\n        client: Client,\n        message: dict\n    ) -> None:\n        \"\"\"Send JSON message to client.\"\"\"\n        try:\n            await client.websocket.send(json.dumps(message))\n        except Exception as e:\n            print(f\"Error sending to {client.id}: {e}\")\n            await self.disconnect(client)\n    \n    async def send_error(self, client: Client, error: str) -> None:\n        \"\"\"Send error message to client.\"\"\"\n        await self.send_message(\n            client,\n            MessageType.ERROR.value,\n            {\"error\": error}\n        )\n    \n    async def disconnect(self, client: Client) -> None:\n        \"\"\"Handle client disconnection.\"\"\"\n        # Unsubscribe from all topics\n        for topic in list(client.subscriptions):\n            if topic in self.subscriptions:\n                self.subscriptions[topic].discard(client.id)\n        \n        # Remove from clients\n        self.clients.pop(client.id, None)\n        \n        # Close connection\n        try:\n            await client.websocket.close()\n        except Exception:\n            pass\n```\n\n## Socket.IO Implementation\n```typescript\n\u002F\u002F Server-side (Node.js)\nimport { Server } from 'socket.io';\n\n\nconst io = new Server(3000, {\n  cors: {\n    origin: '*',\n    methods: ['GET', 'POST'],\n  },\n  pingTimeout: 60000,\n  pingInterval: 25000,\n});\n\n\n\u002F\u002F Authentication middleware\nio.use((socket, next) => {\n  const token = socket.handshake.auth.token;\n  \n  if (!token) {\n    return next(new Error('Authentication required'));\n  }\n  \n  try {\n    const user = verifyToken(token);\n    socket.user = user;\n    next();\n  } catch (e) {\n    next(new Error('Invalid token'));\n  }\n});\n\n\nio.on('connection', (socket) => {\n  console.log(`User connected: ${socket.user.id}`);\n  \n  \u002F\u002F Join rooms\n  socket.on('join:room', (roomId) => {\n    socket.join(roomId);\n    socket.to(roomId).emit('user:joined', {\n      userId: socket.user.id,\n      roomId,\n    });\n  });\n  \n  \u002F\u002F Leave rooms\n  socket.on('leave:room', (roomId) => {\n    socket.leave(roomId);\n    socket.to(roomId).emit('user:left', {\n      userId: socket.user.id,\n      roomId,\n    });\n  });\n  \n  \u002F\u002F Handle events\n  socket.on('message:send', (data) => {\n    const { roomId, content } = data;\n    \n    const message = {\n      id: generateId(),\n      content,\n      userId: socket.user.id,\n      timestamp: new Date().toISOString(),\n    };\n    \n    \u002F\u002F Broadcast to room\n    io.to(roomId).emit('message:received', message);\n  });\n  \n  \u002F\u002F Typing indicators\n  socket.on('typing:start', (roomId) => {\n    socket.to(roomId).emit('user:typing', {\n      userId: socket.user.id,\n      isTyping: true,\n    });\n  });\n  \n  socket.on('typing:stop', (roomId) => {\n    socket.to(roomId).emit('user:typing', {\n      userId: socket.user.id,\n      isTyping: false,\n    });\n  });\n  \n  \u002F\u002F Handle disconnection\n  socket.on('disconnect', (reason) => {\n    console.log(`User disconnected: ${socket.user.id}, reason: ${reason}`);\n    \n    \u002F\u002F Notify rooms\n    socket.rooms.forEach((roomId) => {\n      if (roomId !== socket.id) {\n        socket.to(roomId).emit('user:left', {\n          userId: socket.user.id,\n          roomId,\n        });\n      }\n    });\n  });\n});\n```\n\n## Client Implementation\n```typescript\n\u002F\u002F Client-side (React)\nimport { useEffect, useRef, useState, useCallback } from 'react';\nimport io, { Socket } from 'socket.io-client';\n\n\ninterface UseSocketOptions {\n  url: string;\n  token: string;\n  autoConnect?: boolean;\n}\n\n\nexport function useSocket({\n  url,\n  token,\n  autoConnect = true,\n}: UseSocketOptions) {\n  const socketRef = useRef\u003CSocket | null>(null);\n  const [isConnected, setIsConnected] = useState(false);\n  const [lastMessage, setLastMessage] = useState\u003Cany>(null);\n  \n  useEffect(() => {\n    socketRef.current = io(url, {\n      auth: { token },\n      transports: ['websocket'],\n      reconnection: true,\n      reconnectionAttempts: 5,\n      reconnectionDelay: 1000,\n    });\n    \n    socketRef.current.on('connect', () => {\n      setIsConnected(true);\n      console.log('Connected to WebSocket server');\n    });\n    \n    socketRef.current.on('disconnect', (reason) => {\n      setIsConnected(false);\n      console.log('Disconnected:', reason);\n    });\n    \n    socketRef.current.on('message:received', (message) => {\n      setLastMessage(message);\n    });\n    \n    if (autoConnect) {\n      socketRef.current.connect();\n    }\n    \n    return () => {\n      socketRef.current?.disconnect();\n    };\n  }, [url, token, autoConnect]);\n  \n  const joinRoom = useCallback((roomId: string) => {\n    socketRef.current?.emit('join:room', roomId);\n  }, []);\n  \n  const leaveRoom = useCallback((roomId: string) => {\n    socketRef.current?.emit('leave:room', roomId);\n  }, []);\n  \n  const sendMessage = useCallback((roomId: string, content: string) => {\n    socketRef.current?.emit('message:send', { roomId, content });\n  }, []);\n  \n  const on = useCallback((event: string, callback: (...args: any[]) => void) => {\n    socketRef.current?.on(event, callback);\n    return () => {\n      socketRef.current?.off(event, callback);\n    };\n  }, []);\n  \n  return {\n    isConnected,\n    lastMessage,\n    joinRoom,\n    leaveRoom,\n    sendMessage,\n    on,\n    socket: socketRef.current,\n  };\n}\n```\n\n## Scaling WebSockets\n```python\n# Using Redis Pub\u002FSub for horizontal scaling\nimport aioredis\nimport json\n\n\nclass RedisPubSubManager:\n    \"\"\"Redis Pub\u002FSub for multi-instance WebSocket scaling.\"\"\"\n    \n    def __init__(self, redis_url: str) -> None:\n        self.redis = aioredis.from_url(redis_url)\n        self.pubsub = self.redis.pubsub()\n        self.subscriptions: Dict[str, Set[str]] = {}\n        self.client_messages: asyncio.Queue = asyncio.Queue()\n    \n    async def subscribe(self, topic: str, server_id: str) -> None:\n        \"\"\"Subscribe to topic across all servers.\"\"\"\n        await self.redis.subscribe(topic)\n        \n        if topic not in self.subscriptions:\n            self.subscriptions[topic] = set()\n        \n        self.subscriptions[topic].add(server_id)\n    \n    async def publish(self, topic: str, message: dict) -> None:\n        \"\"\"Publish message to topic.\"\"\"\n        await self.redis.publish(\n            topic,\n            json.dumps({\n                'server_id': self.server_id,\n                'message': message,\n            })\n        )\n    \n    async def handle_messages(self) -> None:\n        \"\"\"Handle incoming pub\u002Fsub messages.\"\"\"\n        async for message in self.pubsub.listen():\n            if message['type'] == 'message':\n                data = json.loads(message['data'])\n                \n                # If from another server, relay to local clients\n                if data['server_id'] != self.server_id:\n                    await self.relay_to_local_clients(\n                        message['channel'],\n                        data['message']\n                    )\n```\n\n## Best Practices\n```\n1. Use secure WebSockets (wss:\u002F\u002F)\n   Always use TLS in production\n\n2. Authenticate connections\n   Validate tokens on connection\n\n3. Implement heartbeat\u002Fping\n   Detect dead connections quickly\n\n4. Handle reconnection\n   Exponential backoff with jitter\n\n5. Limit connection lifetime\n   Require re-authentication periodically\n\n6. Scale horizontally\n   Use Redis Pub\u002FSub for broadcasting\n\n7. Monitor connections\n   Track connection counts, message rates\n\n8. Set message size limits\n   Prevent memory exhaustion\n\n9. Handle backpressure\n   Don't overwhelm slow clients\n\n10. Graceful shutdown\n    Notify clients before disconnecting\n```\n",{"name":5,"license":14,"metadata":29,"description":7,"compatibility":21},{"audience":30,"category":20},"developers","main","5f7a3e453c98261f93d19a6a7de44ad8f7b72d41",2,"https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk","2026-03-01 06:55:40",{"source":8,"skillName":5,"commands":38,"prompt":48,"zipAvailable":49},[39,42,45],{"tool":40,"command":41},"npx","npx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill websockets",{"tool":43,"command":44},"bunx","bunx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill websockets",{"tool":46,"command":47},"pnpm","pnpm dlx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill websockets","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwebsockets\u002FSKILL.md",true,[51,57,63,67,71,75,79,83,88,94,100,104,108,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168],{"path":52,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":55,"sourceHtmlUrl":56},".gitignore","OTHER","其他",494,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002F.gitignore",{"path":58,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":61,"sourceHtmlUrl":62},"assets\u002Freview-ui.png","ASSET","资产文件",1410108,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Freview-ui.png",{"path":64,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":65,"sourceHtmlUrl":66},"assets\u002Fvolc-1-overview.png",641083,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Fvolc-1-overview.png",{"path":68,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":69,"sourceHtmlUrl":70},"assets\u002Fvolc-2-enable.png",449679,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Fvolc-2-enable.png",{"path":72,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":73,"sourceHtmlUrl":74},"assets\u002Fvolc-3-standard.png",224167,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Fvolc-3-standard.png",{"path":76,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":77,"sourceHtmlUrl":78},"assets\u002Fvolc-4-flash.png",247180,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Fvolc-4-flash.png",{"path":80,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":81,"sourceHtmlUrl":82},"assets\u002Fvolc-5-apikey.png",382488,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fassets\u002Fvolc-5-apikey.png",{"path":84,"resourceType":84,"resourceTypeDesc":85,"sizeBytes":86,"sourceHtmlUrl":87},"LICENSE","许可证",34523,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002FLICENSE",{"path":89,"resourceType":90,"resourceTypeDesc":91,"sizeBytes":92,"sourceHtmlUrl":93},"README.md","REFERENCE","参考文档",4811,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002FREADME.md",{"path":95,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":98,"sourceHtmlUrl":99},"scripts\u002Fauto_filler.js","SCRIPT","脚本",4678,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fauto_filler.js",{"path":101,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":102,"sourceHtmlUrl":103},"scripts\u002Fdoctor.js",1058,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fdoctor.js",{"path":105,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":106,"sourceHtmlUrl":107},"scripts\u002Fextract_text.js",1009,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fextract_text.js",{"path":109,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":110,"sourceHtmlUrl":111},"scripts\u002Fgen_analysis.js",1733,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fgen_analysis.js",{"path":113,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":114,"sourceHtmlUrl":115},"scripts\u002Fgen_word_detail.js",1165,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fgen_word_detail.js",{"path":117,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":118,"sourceHtmlUrl":119},"scripts\u002Fgenerate_review.js",8123,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fgenerate_review.js",{"path":121,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":122,"sourceHtmlUrl":123},"scripts\u002Fgenerate_subtitles.js",3361,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fgenerate_subtitles.js",{"path":125,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":126,"sourceHtmlUrl":127},"scripts\u002Flib\u002Fcompute_keeps.js",6353,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Flib\u002Fcompute_keeps.js",{"path":129,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":130,"sourceHtmlUrl":131},"scripts\u002Flib\u002Ffcpxml.js",5212,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Flib\u002Ffcpxml.js",{"path":133,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":134,"sourceHtmlUrl":135},"scripts\u002Flib\u002Frefine_boundaries.js",5954,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Flib\u002Frefine_boundaries.js",{"path":137,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":138,"sourceHtmlUrl":139},"scripts\u002Fmerge_selections.js",1855,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fmerge_selections.js",{"path":141,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":142,"sourceHtmlUrl":143},"scripts\u002Freview_server.js",11883,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Freview_server.js",{"path":145,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":146,"sourceHtmlUrl":147},"scripts\u002Frun_transcribe.sh",1587,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Frun_transcribe.sh",{"path":149,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":150,"sourceHtmlUrl":151},"scripts\u002Fserve_review.sh",4595,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Fserve_review.sh",{"path":153,"resourceType":96,"resourceTypeDesc":97,"sizeBytes":154,"sourceHtmlUrl":155},"scripts\u002Ftemplates\u002Freview.html",76921,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002Fscripts\u002Ftemplates\u002Freview.html",{"path":157,"resourceType":90,"resourceTypeDesc":91,"sizeBytes":158,"sourceHtmlUrl":159},"用户习惯\u002F断行prompt.md",1172,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002F用户习惯\u002F断行prompt.md",{"path":161,"resourceType":90,"resourceTypeDesc":91,"sizeBytes":162,"sourceHtmlUrl":163},"用户习惯\u002F纠错prompt.md",1462,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002F用户习惯\u002F纠错prompt.md",{"path":165,"resourceType":90,"resourceTypeDesc":91,"sizeBytes":166,"sourceHtmlUrl":167},"用户习惯\u002F经验规则.md",1532,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002F用户习惯\u002F经验规则.md",{"path":169,"resourceType":90,"resourceTypeDesc":91,"sizeBytes":170,"sourceHtmlUrl":171},"用户习惯\u002F规则.md",4048,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSpeech-Recognition\u002Fai-jian-koubo\u002F用户习惯\u002F规则.md",[173,184,194,203,213,222],{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":177,"categories":178,"keywords":179,"publishedAt":24},"01M6CXT9TXDF79BWJTMQAPB49G","xamarin","Xamarin cross-platform development","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fxamarin\u002FSKILL.md",[],[175,180,21,181,182,183],"development","mobile","cross-platform","platform",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":190,"publishedAt":24},"01M6CXT8AD04MFYDA32VW3FKAG","wrangling","Data wrangling fundamentals","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwrangling\u002FSKILL.md",[],[186,191,21,192,193],"data wrangling","science","data",{"skillNo":195,"name":196,"nameZh":6,"description":197,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":198,"categories":199,"keywords":200,"publishedAt":24},"01M6CXT717GCN7SRRNPBDP4508","windows","Windows operating system administration and scripting","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwindows\u002FSKILL.md",[],[196,21,201,202],"system","scripting",{"skillNo":204,"name":205,"nameZh":6,"description":206,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":209,"publishedAt":24},"01M6CXT54K794NMEM3XAPZZDKT","wide-column","Wide-column data model","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column\u002FSKILL.md",[],[210,211,21,212,193],"wide column","databases","model",{"skillNo":214,"name":215,"nameZh":6,"description":216,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":217,"categories":218,"keywords":219,"publishedAt":24},"01M6CXT28FMT2P4V9GZE67WA8E","wide-column-stores","Wide-column database stores","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column-stores\u002FSKILL.md",[],[220,211,21,221],"wide column stores","database",{"skillNo":223,"name":224,"nameZh":6,"description":225,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":226,"categories":227,"keywords":228,"publishedAt":24},"01M6CXSW01QQFPB6CPQNJCPYS2","wasm","WebAssembly for high-performance browser code execution","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwasm\u002FSKILL.md",[],[224,229,21,230,231,232,233,180,234,235],"web-development","execution","browser","webassembly","code","web","performance"]