Marketplace

socket-io

Builds real-time applications with Socket.IO including events, rooms, namespaces, and acknowledgements. Use when implementing real-time features, chat applications, live updates, or multiplayer functionality.

$ 安裝

git clone https://github.com/mgd34msu/goodvibes-plugin /tmp/goodvibes-plugin && cp -r /tmp/goodvibes-plugin/plugins/goodvibes/skills/webdev/realtime-websockets/socket-io ~/.claude/skills/goodvibes-plugin

// tip: Run this command in your terminal to install the skill


name: socket-io description: Builds real-time applications with Socket.IO including events, rooms, namespaces, and acknowledgements. Use when implementing real-time features, chat applications, live updates, or multiplayer functionality.

Socket.IO

Real-time bidirectional event-based communication library.

Quick Start

Install Server:

npm install socket.io

Install Client:

npm install socket.io-client

Server Setup

Basic Server (Node.js)

// server.ts
import { createServer } from 'http';
import { Server } from 'socket.io';

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);

  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});

httpServer.listen(3001, () => {
  console.log('Socket.IO server running on port 3001');
});

Express Integration

import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';

const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
  cors: {
    origin: 'http://localhost:3000',
  },
});

app.get('/', (req, res) => {
  res.send('Hello World');
});

io.on('connection', (socket) => {
  console.log('Connected:', socket.id);
});

httpServer.listen(3001);

Next.js API Route

// pages/api/socket.ts (Pages Router)
import { Server } from 'socket.io';
import type { NextApiRequest, NextApiResponse } from 'next';
import type { Server as HTTPServer } from 'http';
import type { Socket as NetSocket } from 'net';

interface SocketServer extends HTTPServer {
  io?: Server;
}

interface SocketWithIO extends NetSocket {
  server: SocketServer;
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (!(res.socket as SocketWithIO).server.io) {
    const io = new Server((res.socket as SocketWithIO).server, {
      path: '/api/socket',
      addTrailingSlash: false,
    });

    io.on('connection', (socket) => {
      console.log('Connected:', socket.id);

      socket.on('message', (data) => {
        io.emit('message', data);
      });
    });

    (res.socket as SocketWithIO).server.io = io;
  }

  res.end();
}

Client Setup

Browser Client

import { io } from 'socket.io-client';

const socket = io('http://localhost:3001');

socket.on('connect', () => {
  console.log('Connected:', socket.id);
});

socket.on('disconnect', () => {
  console.log('Disconnected');
});

socket.on('connect_error', (error) => {
  console.error('Connection error:', error);
});

React Hook

// hooks/useSocket.ts
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';

export function useSocket(url: string) {
  const [socket, setSocket] = useState<Socket | null>(null);
  const [isConnected, setIsConnected] = useState(false);

  useEffect(() => {
    const socketInstance = io(url);

    socketInstance.on('connect', () => {
      setIsConnected(true);
    });

    socketInstance.on('disconnect', () => {
      setIsConnected(false);
    });

    setSocket(socketInstance);

    return () => {
      socketInstance.disconnect();
    };
  }, [url]);

  return { socket, isConnected };
}
// components/Chat.tsx
function Chat() {
  const { socket, isConnected } = useSocket('http://localhost:3001');
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    if (!socket) return;

    socket.on('message', (data: string) => {
      setMessages((prev) => [...prev, data]);
    });

    return () => {
      socket.off('message');
    };
  }, [socket]);

  const sendMessage = (text: string) => {
    socket?.emit('message', text);
  };

  return (
    <div>
      <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
      <ul>
        {messages.map((msg, i) => (
          <li key={i}>{msg}</li>
        ))}
      </ul>
    </div>
  );
}

Events

Emit and Listen

// Server
io.on('connection', (socket) => {
  // Receive from client
  socket.on('chat:message', (data) => {
    console.log('Message:', data);
  });

  // Send to client
  socket.emit('chat:message', { text: 'Hello from server' });
});

// Client
socket.emit('chat:message', { text: 'Hello from client' });

socket.on('chat:message', (data) => {
  console.log('Message:', data);
});

Acknowledgements

// Client sends with callback
socket.emit('save:data', { id: 1, name: 'Test' }, (response) => {
  console.log('Server response:', response);
});

// Server handles with callback
socket.on('save:data', (data, callback) => {
  // Process data
  const result = saveToDatabase(data);
  callback({ success: true, id: result.id });
});

With Timeout

// Client with timeout
socket.timeout(5000).emit('request', data, (err, response) => {
  if (err) {
    console.error('Timeout or error');
  } else {
    console.log('Response:', response);
  }
});

Broadcasting

To All Clients

// To all connected clients (including sender)
io.emit('announcement', 'Hello everyone!');

// To all except sender
socket.broadcast.emit('user:joined', { name: 'John' });

To Specific Clients

// To specific socket ID
io.to(socketId).emit('private:message', 'Hello');

// To multiple IDs
io.to(socketId1).to(socketId2).emit('message', 'Hello');

Rooms

Join and Leave

// Server
socket.on('room:join', (roomId) => {
  socket.join(roomId);
  socket.to(roomId).emit('user:joined', { userId: socket.id });
});

socket.on('room:leave', (roomId) => {
  socket.leave(roomId);
  socket.to(roomId).emit('user:left', { userId: socket.id });
});

socket.on('disconnect', () => {
  // Automatically leaves all rooms
});

Send to Room

// To everyone in room (except sender)
socket.to('room-1').emit('chat:message', { text: 'Hello room!' });

// To everyone in room (including sender)
io.in('room-1').emit('announcement', 'Room announcement');

// To everyone in multiple rooms
io.to('room-1').to('room-2').emit('message', 'Hello rooms!');

Get Room Info

// Get sockets in room
const sockets = await io.in('room-1').fetchSockets();
console.log('Users in room:', sockets.length);

// Get rooms a socket is in
const rooms = socket.rooms;
console.log('Socket rooms:', [...rooms]);

Namespaces

Create Namespace

// Server
const chatNamespace = io.of('/chat');
const adminNamespace = io.of('/admin');

chatNamespace.on('connection', (socket) => {
  console.log('Chat connected:', socket.id);
});

adminNamespace.on('connection', (socket) => {
  console.log('Admin connected:', socket.id);
});

// Client
const chatSocket = io('http://localhost:3001/chat');
const adminSocket = io('http://localhost:3001/admin');

Namespace Middleware

adminNamespace.use((socket, next) => {
  const token = socket.handshake.auth.token;

  if (isValidAdminToken(token)) {
    next();
  } else {
    next(new Error('Unauthorized'));
  }
});

Middleware

Server Middleware

// Global middleware
io.use((socket, next) => {
  const token = socket.handshake.auth.token;

  if (isValidToken(token)) {
    socket.data.user = decodeToken(token);
    next();
  } else {
    next(new Error('Authentication error'));
  }
});

// Access user data
io.on('connection', (socket) => {
  console.log('User:', socket.data.user);
});

Error Handling

// Client handles errors
socket.on('connect_error', (error) => {
  if (error.message === 'Authentication error') {
    // Redirect to login
  }
});

Chat Application Example

Server

interface User {
  id: string;
  name: string;
}

interface Message {
  id: string;
  userId: string;
  userName: string;
  text: string;
  timestamp: Date;
}

const users = new Map<string, User>();

io.on('connection', (socket) => {
  socket.on('user:join', (name: string) => {
    const user = { id: socket.id, name };
    users.set(socket.id, user);

    socket.broadcast.emit('user:joined', user);
    io.emit('users:list', Array.from(users.values()));
  });

  socket.on('message:send', (text: string) => {
    const user = users.get(socket.id);
    if (!user) return;

    const message: Message = {
      id: crypto.randomUUID(),
      userId: user.id,
      userName: user.name,
      text,
      timestamp: new Date(),
    };

    io.emit('message:new', message);
  });

  socket.on('typing:start', () => {
    const user = users.get(socket.id);
    if (user) {
      socket.broadcast.emit('user:typing', user);
    }
  });

  socket.on('typing:stop', () => {
    socket.broadcast.emit('user:stopped-typing', socket.id);
  });

  socket.on('disconnect', () => {
    const user = users.get(socket.id);
    if (user) {
      users.delete(socket.id);
      io.emit('user:left', user);
      io.emit('users:list', Array.from(users.values()));
    }
  });
});

Client

function ChatApp() {
  const { socket, isConnected } = useSocket('http://localhost:3001');
  const [messages, setMessages] = useState<Message[]>([]);
  const [users, setUsers] = useState<User[]>([]);
  const [typingUsers, setTypingUsers] = useState<User[]>([]);

  useEffect(() => {
    if (!socket) return;

    socket.on('message:new', (message: Message) => {
      setMessages((prev) => [...prev, message]);
    });

    socket.on('users:list', (userList: User[]) => {
      setUsers(userList);
    });

    socket.on('user:typing', (user: User) => {
      setTypingUsers((prev) => [...prev, user]);
    });

    socket.on('user:stopped-typing', (userId: string) => {
      setTypingUsers((prev) => prev.filter((u) => u.id !== userId));
    });

    return () => {
      socket.off('message:new');
      socket.off('users:list');
      socket.off('user:typing');
      socket.off('user:stopped-typing');
    };
  }, [socket]);

  const sendMessage = (text: string) => {
    socket?.emit('message:send', text);
  };

  return (
    <div>
      <UserList users={users} />
      <MessageList messages={messages} />
      {typingUsers.length > 0 && (
        <p>{typingUsers.map((u) => u.name).join(', ')} typing...</p>
      )}
      <MessageInput
        onSend={sendMessage}
        onTyping={() => socket?.emit('typing:start')}
        onStopTyping={() => socket?.emit('typing:stop')}
      />
    </div>
  );
}

Best Practices

  1. Use namespaces - Separate concerns (chat, notifications, etc.)
  2. Use rooms for groups - Efficient broadcasting
  3. Implement reconnection - Handle connection drops
  4. Add authentication - Use middleware
  5. Handle errors - Catch and log errors

Common Mistakes

MistakeFix
Missing CORS configAdd cors option to server
Not cleaning up listenersRemove listeners in cleanup
Blocking event loopUse async handlers
Missing error handlingAdd connect_error listener
Memory leaksClean up on disconnect

Reference Files

Repository

mgd34msu
mgd34msu
Author
mgd34msu/goodvibes-plugin/plugins/goodvibes/skills/webdev/realtime-websockets/socket-io
0
Stars
0
Forks
Updated2d ago
Added1w ago