用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UitbreidenOS/UitKit --skill websockets命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Guidelines and instructions for Agent execution state rollback rules
Guidelines and instructions for Agent execution step counters limits
Guidelines and instructions for Agent execution timeout limits setups
基于 SOC 职业分类
正在显示 SKILL.md
| name | websockets |
| description | - Building real-time bidirectional features (chat, live dashboards, notifications, multiplayer) |
// server/socket.ts
import { Server, Socket } from "socket.io";
import { createServer } from "http";
import { verify } from "jsonwebtoken";
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: process.env.CLIENT_URL ?? "http://localhost:3000",
methods: ["GET", "POST"],
credentials: true,
},
pingTimeout: 20000,
pingInterval: 25000,
transports: ["websocket", "polling"], // websocket first, polling fallback
});
// Authentication middleware — runs before the connection event
io.use(async (socket, next) => {
try {
const token =
socket.handshake.auth.token ??
socket.handshake.headers.authorization?.replace("Bearer ", "");
if (!token) return next(new Error("Authentication required"));
const payload = verify(token, process.env.JWT_SECRET!) as { userId: string; role: string };
socket.data.userId = payload.userId;
socket.data.role = payload.role;
next();
} catch {
next(new Error("Invalid token"));
}
});
io.on("connection", (socket: Socket) => {
const { userId, role } = socket.data;
console.log(`[socket] connected: ${socket.id} user=${userId}`);
socket.on("disconnect", (reason) => {
console.log(`[socket] disconnected: ${socket.id} reason=${reason}`);
});
registerChatHandlers(io, socket);
registerPresenceHandlers(io, socket);
});
export { io, httpServer };
Rooms are server-side namespaces. A socket can be in multiple rooms. Rooms do not persist — they are created on first join and destroyed when empty.
// handlers/chat.ts
export function registerChatHandlers(io: Server, socket: Socket) {
// Join a room
socket.on("chat:join", async (roomId: string) => {
await socket.join(roomId);
// Notify others in the room
socket.to(roomId).emit("chat:user_joined", {
userId: socket.data.userId,
roomId,
});
// Acknowledge to sender
socket.emit("chat:joined", { roomId, success: true });
});
// Send a message to a room
socket.on("chat:message", async (payload: { roomId: string; text: string }) => {
const { roomId, text } = payload;
// Guard: socket must be in the room
if (!socket.rooms.has(roomId)) {
socket.emit("chat:error", { message: "Not in this room" });
return;
}
const message = ({ : socket.., roomId, text });
io.(roomId).(, message);
});
socket.(, {
socket.(roomId);
socket.(roomId).(, { : socket.. });
});
socket.(, {
( room socket.) {
(room !== socket.) {
socket.(room).(, { : socket.. });
}
}
});
}
Broadcast targets:
io.emit("event", data); // all connected sockets
io.to("room-id").emit("event", data); // all sockets in a room
socket.to("room-id").emit("event", data); // room members excluding sender
socket.broadcast.emit("event", data); // all sockets except sender
socket.emit("event", data); // sender only
io.except("room-id").emit("event", data); // all except those in room
Without Redis, events emitted on one server process are not received by sockets connected to another process.
npm install @socket.io/redis-adapter ioredis
// server/socket.ts
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "ioredis";
const pubClient = createClient({ host: process.env.REDIS_HOST, port: 6379 });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// After adding the adapter, io.to("room").emit() works across all server instances
With the Redis adapter, Socket.io serializes events through Redis pub/sub. All server instances share room membership. For room state lookups across instances use io.in("room").fetchSockets() (async).
Additional considerations:
ip_hash, AWS ALB stickiness) when polling is enabled — polling requires all requests from a client to hit the same server during the handshake phasetransports: ["websocket"]), sticky sessions are not requiredSocket.io client handles reconnection automatically. Configure it explicitly:
// client/socket.ts
import { io, Socket } from "socket.io-client";
let socket: Socket;
export function createSocket(token: string): Socket {
socket = io(process.env.NEXT_PUBLIC_API_URL!, {
auth: { token },
transports: ["websocket"],
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000, // initial delay ms
reconnectionDelayMax: 30000, // cap at 30s
randomizationFactor: 0.5, // jitter ±50%
timeout: 10000,
});
socket.on("connect", () => {
console.log("[socket] connected:", socket.id);
// Re-join rooms after reconnect
rejoinRooms();
});
socket.on("connect_error", (err) => {
if (err.message === ) {
socket.();
();
}
.(, err.);
});
socket.(, {
.(, reason);
(reason === ) {
socket.();
}
});
socket;
}
Re-join rooms after reconnect since room membership is stored in memory per server process:
function rejoinRooms() {
const rooms = getRoomStateFromLocalStorage();
rooms.forEach((roomId) => socket.emit("chat:join", roomId));
}
Consistent event naming prevents conflicts and aids debugging. Use namespace:action format:
chat:message
chat:join
chat:leave
chat:typing_start
chat:typing_stop
presence:online
presence:offline
notification:new
room:created
room:deleted
error # reserved by Socket.io — use sparingly
Avoid generic names like data, update, event. Always use lowercase and underscores within the action segment.
Define event types in a shared module:
// shared/socket-events.ts
export interface ServerToClientEvents {
"chat:message": (msg: ChatMessage) => void;
"chat:user_joined": (info: { userId: string; roomId: string }) => void;
"presence:online": (userId: string) => void;
"notification:new": (notif: Notification) => void;
}
export interface ClientToServerEvents {
"chat:join": (roomId: string, callback: (success: boolean) => void) => void;
"chat:message": (payload: { roomId: string; text: string }) => void;
"chat:leave": (roomId: string) => void;
}
// Typed server
const io = <, >(httpServer);
: <, > = ();
Socket.io supports sending binary data (Buffer, ArrayBuffer, Blob, TypedArray) natively:
// Server — send a file buffer
socket.on("file:request", async (fileId: string) => {
const buffer: Buffer = await readFile(fileId);
socket.emit("file:data", { id: fileId, buffer });
});
// Client — receive and use binary
socket.on("file:data", ({ id, buffer }: { id: string; buffer: ArrayBuffer }) => {
const blob = new Blob([buffer]);
const url = URL.createObjectURL(blob);
downloadLink.href = url;
});
// Client — send binary (e.g., audio chunk)
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
const recorder = new MediaRecorder(stream);
recorder.ondataavailable = (e) => {
if (e.data.size > 0) socket.emit("audio:chunk", e.data);
};
recorder.start();
});
For large file transfers, chunk the data manually and reassemble on the receiver. Do not send files larger than a few MB as single events — it saturates the event loop.
A real-time presence and messaging system:
// server — presence handler
export function registerPresenceHandlers(io: Server, socket: Socket) {
socket.on("presence:ping", (roomId: string) => {
socket.to(roomId).emit("presence:online", socket.data.userId);
});
socket.on("disconnecting", () => {
for (const room of socket.rooms) {
if (room !== socket.id) {
io.to(room).emit("presence:offline", socket.data.userId);
}
}
});
}
// client React hook
function useSocket(roomId: string) {
const [online, setOnline] = useState<string[]>([]);
const [messages, setMessages] = useState<ChatMessage[]>([]);
useEffect(() => {
socket.emit("chat:join", roomId);
socket.emit("presence:ping", roomId);
socket.(, ( [...prev, msg]));
socket.(, ( [... ([...prev, id])]));
socket.(, ( prev.( u !== id)));
{
socket.(, roomId);
socket.();
socket.();
socket.();
};
}, [roomId]);
= () => socket.(, { roomId, text });
{ online, messages, send };
}