From qe-framework
Builds WebSocket/Socket.IO systems with Redis pub/sub scaling, authentication, room management, presence tracking, and monitoring.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qe-framework:Qwebsocket-engineerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. **Analyze requirements** — Identify connection scale, message volume, latency needs
npx wscat -c ws://localhost:3000); confirm auth rejection on missing/invalid tokens, room join/leave events, and message deliveryLoad detailed guidance based on context:
| Topic | Reference | Load When |
|---|---|---|
| Protocol | references/protocol.md | WebSocket handshake, frames, ping/pong, close codes |
| Scaling | references/scaling.md | Horizontal scaling, Redis pub/sub, sticky sessions |
| Patterns | references/patterns.md | Rooms, namespaces, broadcasting, acknowledgments |
| Security | references/security.md | Authentication, authorization, rate limiting, CORS |
| Alternatives | references/alternatives.md | SSE, long polling, when to choose WebSockets |
import { createServer } from "http";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
import jwt from "jsonwebtoken";
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: process.env.ALLOWED_ORIGIN, credentials: true },
pingTimeout: 20000,
pingInterval: 25000,
});
// Authentication middleware — runs before connection is established
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error("Authentication required"));
try {
socket.data.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
next(new Error("Invalid token"));
}
});
// Redis adapter for horizontal scaling
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
io.on("connection", (socket) => {
const { userId } = socket.data.user;
console.log(`connected: ${userId} (${socket.id})`);
// Presence: mark user online
pubClient.hSet("presence", userId, socket.id);
socket.on("join-room", (roomId) => {
socket.join(roomId);
socket.to(roomId).emit("user-joined", { userId });
});
socket.on("message", ({ roomId, text }) => {
io.to(roomId).emit("message", { userId, text, ts: Date.now() });
});
socket.on("disconnect", () => {
pubClient.hDel("presence", userId);
console.log(`disconnected: ${userId}`);
});
});
httpServer.listen(3000);
import { io } from "socket.io-client";
const socket = io("wss://api.example.com", {
auth: { token: getAuthToken() },
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000, // initial delay (ms)
reconnectionDelayMax: 30000, // cap at 30 s
randomizationFactor: 0.5, // jitter to avoid thundering herd
});
// Queue messages while disconnected
let messageQueue = [];
socket.on("connect", () => {
console.log("connected:", socket.id);
// Flush queued messages
messageQueue.forEach((msg) => socket.emit("message", msg));
messageQueue = [];
});
socket.on("disconnect", (reason) => {
console.warn("disconnected:", reason);
if (reason === "io server disconnect") socket.connect(); // manual reconnect
});
socket.on("connect_error", (err) => {
console.error("connection error:", err.message);
});
function sendMessage(roomId, text) {
const msg = { roomId, text };
if (socket.connected) {
socket.emit("message", msg);
} else {
messageQueue.push(msg); // buffer until reconnected
}
}
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (ws) => {
ws.on("message", (data) => wss.clients.forEach(c => c.send(data)));
ws.on("close", () => console.log("client disconnected"));
});
setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
ws.on("pong", () => { ws.isAlive = true; });
socket.on("subscribe", (room) => {
socket.join(room);
io.to(room).emit("joined", { user: socket.id });
});
socket.on("publish", (room, msg) => {
io.to(room).emit("message", msg); // only room members receive
});
/**
* Handles incoming message event
* @event message
* @param {Object} data - message payload { roomId, text, ts }
* @emits message - broadcasts to all clients in room
* @throws {Error} if roomId missing or not authorized
*/
socket.on("message", ({ roomId, text }) => {
// validate and broadcast
});
npm run lint:types before committing WebSocket codeCORS config restricts handshake to allowed domainshandshake.auth or query params before emitting eventsmaxPayload in WebSocket config; reject oversized messagessecure: true for Socket.IO over HTTPS| ❌ Wrong | ✅ Correct |
|---|---|
| No heartbeat; stale connections linger | Implement ping/pong every 30s; terminate isAlive === false |
io.emit() broadcasts to all clients | Use io.to(room).emit() to scope by room/channel |
| Blocking event loop in handler (sync I/O) | Use async/await; query DB outside event handler |
| Client reconnects without backoff strategy | Exponential backoff + jitter in Socket.IO client config |
| Store all state in server memory | Use Redis for presence, rooms, and persistent data |
Socket.IO, ws, uWebSockets.js, Redis adapter, sticky sessions, nginx WebSocket proxy, JWT over WebSocket, rooms/namespaces, acknowledgments, binary data, compression, heartbeat, backpressure, horizontal pod autoscaling
npx claudepluginhub inho-team/qe-framework --plugin qe-frameworkCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.