mcpField
W
websocket-engineer

websocket-engineer

github.com/farmage開発224294 回インストール5 週間前に更新
リポジトリ

websocket-engineer について

リアルタイムシステムを設計、実装、拡張する開発者向けの WebSocket エンジニアリングスキルです。接続のセキュリティ、メッセージパターン、検証、運用監視を扱います。

主な機能

  1. 01接続規模と構成の計画
  2. 02認証とルーム管理
  3. 03ローカル接続とイベントの検証
  4. 04Redis Pub/Sub とスティッキーセッション
  5. 05レイテンシとエラーの監視

活用シーン

  1. 01リアルタイム WebSocket 構成の設計
  2. 02認証とルーム動作のテスト
  3. 03複数インスタンスでの Socket.IO 拡張
  4. 04接続急増や障害の監視

インストール

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

$npx skills add https://github.com/farmage/opencode-skills --skill websocket-engineer

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

同梱ファイル

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

references/
alternatives.md
patterns.md
protocol.md
scaling.md
security.md

詳細

WebSocket エンジニア

コアワークフロー

  1. 要件を分析する — 接続規模、メッセージ量、レイテンシの要件を特定します
  2. アーキテクチャを設計する — クラスタリング、pub/sub、状態管理、フェイルオーバーを計画します
  3. 実装する — 認証、ルーム、イベントを備えた WebSocket サーバーを構築します
  4. ローカルで検証する — スケールする前に、接続処理、認証、ルームの動作をテストします(例:npx wscat -c ws://localhost:3000)。認証トークンがない場合や無効な場合に拒否されること、ルームへの参加・退出イベント、メッセージ配信を確認します
  5. スケールする — アダプターを有効にする前に、Redis 接続と pub/sub の往復を検証します。スティッキーセッションを設定し、複数のインスタンス間でテスト接続して確認します。ロードバランシングを設定します
  6. 監視する — 接続数、レイテンシ、スループット、エラー率を追跡します。接続数の急増やエラー率のしきい値に対するアラートを追加します

リファレンスガイド

状況に応じて詳細なガイダンスを読み込みます。

トピックリファレンス読み込むタイミング
プロトコルreferences/protocol.mdWebSocket ハンドシェイク、フレーム、ping/pong、クローズコード
スケーリングreferences/scaling.md水平スケーリング、Redis pub/sub、スティッキーセッション
パターンreferences/patterns.mdルーム、名前空間、ブロードキャスト、確認応答
セキュリティreferences/security.md認証、認可、レート制限、CORS
代替手段references/alternatives.mdSSE、ロングポーリング、WebSocket を選ぶ場面

コード例

サーバーのセットアップ(認証とルーム管理を備えた Socket.IO)
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
  }
}

制約

必ず行うこと
  • ロードバランシングにはスティッキーセッションを使用します(WebSocket 接続はステートフルなため、リクエストは同じサーバーインスタンスにルーティングする必要があります)
  • デッド接続を検出するためにハートビート/ping-pong を実装します(TCP キープアライブだけでは不十分です)
  • アプリケーションロジックでフィルタリングするのではなく、メッセージのスコープ設定にはルーム/名前空間を使用します
  • サイレントなデータ損失を防ぐため、切断中のメッセージをキューに入れます
  • 水平スケーリングの前に、インスタンスごとの接続上限を計画します
絶対に行わないこと
  • クラスタリング戦略なしに大きな状態をメモリに保存します(Redis または外部ストアを使用してください)
  • 明示的なアップグレード処理なしに、同じポートで WebSocket と HTTP を混在させます
  • 接続のクリーンアップを忘れます(プレゼンス記録、ルームのメンバーシップ、実行中のタイマー)
  • 本番環境の前に負荷テストを省略します — 接続数の急増は HTTP トラフィックの急増とは異なる挙動をします

出力テンプレート

WebSocket 機能を実装する際は、次の内容を提示します。

  1. サーバーのセットアップ(Socket.IO/ws の設定)
  2. イベントハンドラー(接続、メッセージ、切断)
  3. クライアントライブラリ(接続、イベント、再接続)
  4. スケーリング戦略の簡潔な説明

ナレッジリファレンス

Socket.IO、ws、uWebSockets.js、Redis アダプター、スティッキーセッション、nginx WebSocket プロキシ、WebSocket 上の JWT、ルーム/名前空間、確認応答、バイナリデータ、圧縮、ハートビート、バックプレッシャー、水平ポッド自動スケーリング

ドキュメント

コメント