mcpField
W
websockets

websockets

github.com/pluginagentmarketplace開発283 回インストール43 週間前に更新
リポジトリ

websockets について

Node.js開発者向けのWebSocket学習スキルです。Socket.ioとwsのサーバー・クライアント構成を扱い、接続管理からルーム、認証、Redisによる拡張、テストまでを解説します。

主な機能

  1. 01Socket.ioとwsのサーバー構築
  2. 02クライアント接続とイベント送受信の管理
  3. 03ルームと名前空間による通信の整理
  4. 04認証ミドルウェアと再接続の設定
  5. 05Redis Adapterによる拡張とサーバーテスト

活用シーン

  1. 01リアルタイムチャットアプリの構築
  2. 02通知やダッシュボード指標のリアルタイム配信
  3. 03共同作業アプリでの双方向通信の実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pluginagentmarketplace/custom-plugin-nodejs --skill websockets

インストール後、Claude Code で/websocketsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
config.yaml
schema.json
references/
GUIDE.md
PATTERNS.md
scripts/
validate.py

詳細

Node.js WebSockets スキル

チャットアプリ、ライブ通知、コラボレーションツール、リアルタイムダッシュボードを構築するための、リアルタイム双方向通信を習得します。

クイックスタート

WebSocket サーバーを3ステップで構築します。

  1. サーバーをセットアップする - Socket.io または ws ライブラリ
  2. 接続を処理する - クライアントのライフサイクルを管理する
  3. イベントを送信する - メッセージを送受信する

コアコンセプト

Socket.io サーバーのセットアップ
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');

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

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

  // Handle events
  socket.on('chat:message', (data) => {
    // Broadcast to all clients
    io.emit('chat:message', {
      ...data,
      timestamp: Date.now()
    });
  });

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

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

httpServer.listen(3000);
Socket.io クライアント
import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  auth: { token: 'jwt-token-here' }
});

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

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

// Send message
socket.emit('chat:message', {
  text: 'Hello!',
  userId: 'user123'
});

学習ロードマップ

初級(1~2週間)
  • ✅ Socket.io サーバーのセットアップ
  • ✅ 基本的なイベント送信
  • ✅ 接続/切断の処理
  • ✅ シンプルなチャットアプリケーション
中級(3~4週間)
  • ✅ ルームと名前空間
  • ✅ 認証ミドルウェア
  • ✅ ブロードキャストのパターン
  • ✅ エラー処理
上級(5~6週間)
  • ✅ Redis による水平スケーリング
  • ✅ バイナリデータ転送
  • ✅ 再接続の戦略
  • ✅ パフォーマンスの最適化

ネイティブの ws ライブラリ

const WebSocket = require('ws');
const { createServer } = require('http');

const server = createServer();
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws, req) => {
  const ip = req.socket.remoteAddress;
  console.log('Client connected from', ip);

  // Handle messages
  ws.on('message', (data) => {
    const message = JSON.parse(data);
    console.log('Received:', message);

    // Echo back
    ws.send(JSON.stringify({
      type: 'echo',
      data: message
    }));
  });

  // Ping/pong heartbeat
  ws.isAlive = true;
  ws.on('pong', () => {
    ws.isAlive = true;
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

// Heartbeat interval
const interval = setInterval(() => {
  wss.clients.forEach((ws) => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('close', () => clearInterval(interval));

server.listen(3000);

ルームと名前空間

// Namespaces for feature separation
const chatNamespace = io.of('/chat');
const notificationsNamespace = io.of('/notifications');

chatNamespace.on('connection', (socket) => {
  // Chat-specific logic
  socket.on('message', (msg) => {
    chatNamespace.emit('message', msg);
  });
});

notificationsNamespace.on('connection', (socket) => {
  // Notification-specific logic
  socket.on('subscribe', (topic) => {
    socket.join(topic);
  });
});

// Rooms within namespace
socket.on('join-channel', (channelId) => {
  socket.join(`channel:${channelId}`);

  // Send only to room
  io.to(`channel:${channelId}`).emit('user-joined', {
    userId: socket.userId,
    channelId
  });
});

// Leave room
socket.on('leave-channel', (channelId) => {
  socket.leave(`channel:${channelId}`);
});

認証ミドルウェア

const jwt = require('jsonwebtoken');

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

  if (!token) {
    return next(new Error('Authentication required'));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    socket.userId = decoded.id;
    socket.userRole = decoded.role;
    next();
  } catch (err) {
    next(new Error('Invalid token'));
  }
});

// Namespace-level auth
const adminNamespace = io.of('/admin');
adminNamespace.use((socket, next) => {
  if (socket.userRole !== 'admin') {
    return next(new Error('Admin access required'));
  }
  next();
});

Redis アダプターによるスケーリング

const { createAdapter } = require('@socket.io/redis-adapter');
const { createClient } = require('redis');

const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();

Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
  io.adapter(createAdapter(pubClient, subClient));
  console.log('Redis adapter connected');
});

// Now events are broadcast across all server instances
io.emit('notification', { message: 'Hello from any server!' });

リアルタイムのパターン

チャットアプリケーション
// Server
io.on('connection', (socket) => {
  socket.on('chat:join', ({ room, username }) => {
    socket.join(room);
    socket.to(room).emit('chat:user-joined', { username });
  });

  socket.on('chat:message', ({ room, message }) => {
    io.to(room).emit('chat:message', {
      user: socket.userId,
      message,
      timestamp: Date.now()
    });
  });

  socket.on('chat:typing', ({ room }) => {
    socket.to(room).emit('chat:typing', { user: socket.userId });
  });
});
ライブ通知
// Server
function sendNotification(userId, notification) {
  io.to(`user:${userId}`).emit('notification', notification);
}

// Join user's personal room
socket.on('authenticate', () => {
  socket.join(`user:${socket.userId}`);
});

// From any service
notificationService.on('new', (userId, data) => {
  sendNotification(userId, data);
});
ライブダッシュボード
// Server: broadcast metrics periodically
setInterval(async () => {
  const metrics = await getSystemMetrics();
  io.emit('metrics:update', metrics);
}, 1000);

// Or on-demand updates
database.onChange((change) => {
  io.to(`dashboard:${change.collection}`).emit('data:update', change);
});

エラー処理

// Client-side reconnection
const socket = io({
  reconnection: true,
  reconnectionAttempts: 5,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000
});

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

socket.on('reconnect', (attemptNumber) => {
  console.log('Reconnected after', attemptNumber, 'attempts');
});

socket.on('reconnect_failed', () => {
  console.log('Failed to reconnect');
  // Fallback to polling or show offline UI
});

// Server-side error handling
socket.on('error', (error) => {
  console.error('Socket error:', error);
});

単体テストのテンプレート

const { createServer } = require('http');
const { Server } = require('socket.io');
const Client = require('socket.io-client');

describe('WebSocket Server', () => {
  let io, serverSocket, clientSocket;

  beforeAll((done) => {
    const httpServer = createServer();
    io = new Server(httpServer);
    httpServer.listen(() => {
      const port = httpServer.address().port;
      clientSocket = Client(`http://localhost:${port}`);
      io.on('connection', (socket) => {
        serverSocket = socket;
      });
      clientSocket.on('connect', done);
    });
  });

  afterAll(() => {
    io.close();
    clientSocket.close();
  });

  it('should receive message from client', (done) => {
    serverSocket.on('hello', (arg) => {
      expect(arg).toBe('world');
      done();
    });
    clientSocket.emit('hello', 'world');
  });

  it('should broadcast to clients', (done) => {
    clientSocket.on('broadcast', (arg) => {
      expect(arg).toBe('everyone');
      done();
    });
    io.emit('broadcast', 'everyone');
  });
});

トラブルシューティング

問題原因解決策
接続が切断されるハートビートがないpingInterval/pingTimeout を有効にする
メッセージを受信できないルームが間違っているルームへの参加状況を確認する
スケーリングの問題Redis がないRedis アダプターを追加する
メモリーリークリスナーが削除されていない切断時にクリーンアップする

使用する場面

次の場合に WebSockets を使用します。

  • リアルタイムの双方向通信が必要
  • 低レイテンシーの更新が必要
  • ライブ通知/チャット
  • コラボレーションアプリケーション
  • ゲーム/取引プラットフォーム

関連スキル

  • Express REST API(HTTP フォールバック)
  • マイクロサービス(分散イベント)
  • Redis(pub/sub によるスケーリング)

リソース

コメント