Skip to main content

websocket-patterns

WebSocket and real-time patterns. Socket.io, native WebSocket, Server-Sent Events, connection management, rooms.

Source facts

Repository
skeletorflet/opencode-kit
Last source activity
April 3, 2026 at 15:32
Detected SKILL.md language
English
Stars
10
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
websocket-patterns
description
WebSocket and real-time patterns. Socket.io, native WebSocket, Server-Sent Events, connection management, rooms.
# WebSocket Patterns > Use the right real-time primitive. Not everything needs a WebSocket. --- ## 1. Real-Time Technology Choice | Technology | Direction | When | |-----------|-----------|------| | **WebSocket** | Bidirectional | Chat, gaming, live collab | | **Server-Sent Events (SSE)** | Server → Client | Live feeds, notifications | | **HTTP Polling** | Client-initiated | Simple, compatibility | | **Long Polling** | Client-initiated | When WebSocket blocked | ``` SSE is simpler than WebSocket for one-way data. Use WebSocket only when client needs to send data frequently. ``` --- ## 2. Socket.io Server ```ts import { Server } from "socket.io"; import { createServer } from "http"; const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: process.env.CLIENT_URL, credentials: true }, pingTimeout: 60_000, pingInterval: 25_000, }); // Authentication middleware io.use(async (socket, next) => { try { const token = socket.handshake.auth.token; const user = await verifyToken(token); socket.data.user = user; next(); } catch { next(new Error("Authentication error")); } }); io.on("connection", (socket) => { const { user } = socket.data; console.log(`${user.id} connected`); // Join room socket.on("join:room", (roomId: string) => { socket.join(roomId); socket.to(roomId).emit("user:joined", { userId: user.id }); }); // Message socket.on("message:send", async ({ roomId, content }) => { const message = await db.message.create({ data: { content, authorId: user.id, roomId }, include: { author: true }, }); io.to(roomId).emit("message:new", message); }); socket.on("disconnect", (reason) => { console.log(`${user.id} disconnected: ${reason}`); }); }); ``` --- ## 3. Socket.io Client (React) ```tsx import { createContext, useContext, useEffect, useState } from "react"; import { io, Socket } from "socket.io-client"; const SocketContext = createContext<Socket | null>(null); export function SocketProvider({ children }: { children: React.ReactNode }) { const [socket, setSocket] = useState<Socket | null>(null); const { token } = useAuth(); useEffect(() => { if (!token) return; const s = io(process.env.NEXT_PUBLIC_WS_URL!, { auth: { token }, transports: ["websocket"], reconnectionAttempts: 5, reconnectionDelay: 1000, }); s.on("connect", () => console.log("Connected")); s.on("connect_error", (err) => console.error("Connection error:", err)); setSocket(s); return () => { s.disconnect(); }; }, [token]); return <SocketContext.Provider value={socket}>{children}</SocketContext.Provider>; } // Usage in component function ChatRoom({ roomId }: { roomId: string }) { const socket = useContext(SocketContext); const [messages, setMessages] = useState<Message[]>([]); useEffect(() => { if (!socket) return; socket.emit("join:room", roomId); socket.on("message:new", (msg) => setMessages(prev => [...prev, msg])); return () => { socket.off("message:new"); socket.emit("leave:room", roomId); }; }, [socket, roomId]); } ``` --- ## 4. Server-Sent Events (SSE) ```ts // Express SSE endpoint app.get("/events", auth, (req, res) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.flushHeaders(); // Send event const send = (event: string, data: unknown) => { res.write(`event: ${event}\\\ data: ${JSON.stringify(data)}\\\ \\\ `); }; // Subscribe to updates const unsubscribe = pubSub.subscribe(`user:${req.user.id}`, (event) => { send(event.type, event.data); }); // Heartbeat to prevent timeout const heartbeat = setInterval(() => res.write(": ping\\\ \\\ "), 30_000); req.on("close", () => { clearInterval(heartbeat); unsubscribe(); }); }); ``` ```tsx // Client SSE useEffect(() => { const es = new EventSource("/events", { withCredentials: true }); es.addEventListener("notification", (e) => { addNotification(JSON.parse(e.data)); }); es.onerror = () => setTimeout(() => es.close(), 1000); // reconnect return () => es.close(); }, []); ``` --- ## 5. Scaling WebSockets ``` Single server: Socket.io in-process Multiple servers: Need sticky sessions + Redis adapter Redis adapter (Socket.io): import { createAdapter } from "@socket.io/redis-adapter"; io.adapter(createAdapter(pubClient, subClient)); → Events now propagate across all server instances → io.to(room).emit() works across servers ``` --- ## 6. Connection Management ``` Reconnection: ├── Exponential backoff (1s, 2s, 4s, 8s, max 30s) ├── Stop retry after N attempts ├── Notify user if offline for extended period └── Re-sync state on reconnect (not just resume) Presence: ├── Track connected sockets per user in Redis ├── Publish presence events on connect/disconnect └── Debounce disconnect (user may have briefly lost connection) ``` --- ## 7. Anti-Patterns | ❌ Don\'t | ✅ Do | |----------|-------| | WebSocket for read-only data | SSE (simpler) | | No auth on WebSocket | Auth in middleware | | Emit to all clients | Use rooms/namespaces | | No reconnect logic | Always implement reconnect | | Store socket connections in DB | Use in-memory + Redis |
View on GitHub