Back to skills
SKILL.md
Events Websocket Pattern
ASecurity> Implement bidirectional real-time communication using WebSocket protocol and Socket.io.
- 20 stars
- 0 votes
- 0 copies
- 0 views
- Added September 11, 2026
Works with
Security analysis
100/100Pro scans all 2 files and shows the line behind each finding
npx -y skills add Intense-Visions/harness-engineering --skill events-websocket-pattern --agent claude-codeAre you the author of Events Websocket Pattern?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/intense-visions-events-websocket-pattern)# Events: WebSocket Pattern
> Implement bidirectional real-time communication using WebSocket protocol and Socket.io.
## When to Use
- You need real-time, bidirectional communication between server and client
- Chat, collaborative editing, live dashboards, multiplayer games, live order tracking
- The client needs to push data to the server frequently (not just receive)
- You need low-latency updates without polling
- Use SSE instead if communication is one-way (server → client only)
## Instructions
**Native WebSocket server (ws library):**
```typescript
import { WebSocketServer, WebSocket } from 'ws';
import { IncomingMessage } from 'http';
interface AuthenticatedWebSocket extends WebSocket {
userId?: string;
isAlive?: boolean;
}
const wss = new WebSocketServer({ port: 8080 });
// Message types for type safety
type ClientMessage =
| { type: 'join_room'; roomId: string }
| { type: 'leave_room'; roomId: string }
| { type: 'send_message'; roomId: string; content: string };
type ServerMessage =
| { type: 'message'; roomId: string; userId: string; content: string; timestamp: string }
| { type: 'user_joined'; roomId: string; userId: string }
| { type: 'error'; message: string };
// Room management
const rooms = new Map<string, Set<AuthenticatedWebSocket>>();
wss.on('connection', (ws: AuthenticatedWebSocket, req: IncomingMessage) => {
// Authenticate via token in query string or header
const token = new URL(req.url!, 'ws://localhost').searchParams.get('token');
const userId = verifyToken(token);
if (!userId) {
ws.close(4001, 'Unauthorized');
return;
}
ws.userId = userId;
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
}); // heartbeat response
ws.on('message', (data) => {
try {
const msg = JSON.parse(data.toString()) as ClientMessage;
handleMessage(ws, msg);
} catch (err) {
sendToClient(ws, { type: 'error', message: 'Invalid JSON' });
}
});
ws.on('close', () => {
// Remove from all rooms
for (const [roomId, members] of rooms) {
members.delete(ws);
if (members.size === 0) rooms.delete(roomId);
}
});
});
function handleMessage(ws: AuthenticatedWebSocket, msg: ClientMessage): void {
switch (msg.type) {
case 'join_room': {
if (!rooms.has(msg.roomId)) rooms.set(msg.roomId, new Set());
rooms.get(msg.roomId)!.add(ws);
broadcast(msg.roomId, { type: 'user_joined', roomId: msg.roomId, userId: ws.userId! }, ws);
break;
}
case 'send_message': {
const message = {
type: 'message' as const,
roomId: msg.roomId,
userId: ws.userId!,
content: msg.content,
timestamp: new Date().toISOString(),
};
broadcast(msg.roomId, message);
break;
}
}
}
function broadcast(roomId: string, msg: ServerMessage, exclude?: WebSocket): void {
const members = rooms.get(roomId) ?? new Set();
for (const client of members) {
if (client !== exclude && client.readyState === WebSocket.OPEN) {
sendToClient(client, msg);
}
}
}
function sendToClient(ws: WebSocket, msg: ServerMessage): void {
ws.send(JSON.stringify(msg));
}
// Heartbeat to detect stale connections
const heartbeat = setInterval(() => {
wss.clients.forEach((ws: AuthenticatedWebSocket) => {
if (!ws.isAlive) {
ws.terminate();
return;
}
ws.isAlive = false;
ws.ping();
});
}, 30_000);
wss.on('close', () => clearInterval(heartbeat));
```
**Socket.io for rooms and namespaces:**
```typescript
import { Server, Socket } from 'socket.io';
import { createServer } from 'http';
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: 'https://app.example.com', methods: ['GET', 'POST'] },
pingTimeout: 60_000,
pingInterval: 25_000,
});
// Namespace for notifications
const notifNs = io.of('/notifications');
notifNs.use(async (socket: Socket, next) => {
// Middleware for auth
const token = socket.handshake.auth.token;
const userId = await verifyToken(token);
if (!userId) {
next(new Error('Unauthorized'));
return;
}
socket.data.userId = userId;
next();
});
notifNs.on('connection', (socket: Socket) => {
const userId = socket.data.userId as string;
// Join user's personal room
socket.join(`user:${userId}`);
socket.on('disconnect', () => {
console.log(`User ${userId} disconnected`);
});
});
// Push notification from server to specific user
async function pushNotification(userId: string, notification: Notification): Promise<void> {
notifNs.to(`user:${userId}`).emit('notification', notification);
}
```
**Client-side with reconnection:**
```typescript
class WebSocketClient {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
connect(url: string): void {
this.ws = new WebSocket(url);
this.ws.addEventListener('open', () => {
this.reconnectAttempts = 0;
console.log('Connected');
});
this.ws.addEventListener('close', (event) => {
if (!event.wasClean && this.reconnectAttempts < this.maxReconnectAttempts) {
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30_000); // exp backoff
setTimeout(() => {
this.reconnectAttempts++;
this.connect(url);
}, delay);
}
});
this.ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
this.handleMessage(msg);
});
}
send(msg: ClientMessage): void {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(msg));
}
}
private handleMessage(msg: ServerMessage): void {
/* ... */
}
}
```
## Details
**Horizontal scaling:** WebSocket connections are stateful and server-bound. To scale, use Redis adapter for Socket.io or a message broker to route cross-server messages:
```typescript
import { createAdapter } from '@socket.io/redis-adapter';
const pubClient = new Redis(REDIS_URL);
const subClient = pubClient.duplicate();
io.adapter(createAdapter(pubClient, subClient));
```
**Anti-patterns:**
- No heartbeat/ping-pong — dead connections accumulate silently
- Broadcasting to all clients instead of relevant rooms — O(n) sends for irrelevant messages
- No auth on the WebSocket upgrade — anyone can connect
**WebSocket vs. SSE:** WebSocket is bidirectional (client ↔ server). SSE is server→client only. Prefer SSE for live feeds where the client only reads. Use WebSocket for interactive features.
## Source
socket.io/docs/v4/
## Process
1. Read the instructions and examples in this document.
2. Apply the patterns to your implementation, adapting to your specific context.
3. Verify your implementation against the details and edge cases listed above.
## Harness Integration
- **Type:** knowledge — this skill is a reference document, not a procedural workflow.
- **No tools or state** — consumed as context by other skills and agents.
## Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.
Files in this skill
- SKILL.md
- skill.yaml
Attribution
Comments
Loading comments…