web-realtime-socket-io について
Socket.IO クライアントを実装する開発者向けのガイドで、型付きイベント、認証、送達確認、ルーム、名前空間、接続復旧を扱います。クライアント側の実践パターンと、通常の WebSocket との違いを説明します。
主な機能
- 01型付きクライアント・サーバーイベント契約
- 02再接続時の認証情報更新
- 03確認応答付き送信と再試行
- 04ルームと名前空間の管理
- 05接続復旧とライフサイクル処理
活用シーン
- 01型付きリアルタイムメッセージの実装
- 02再接続後の認証情報更新
- 03確認が必要なイベントの送信
- 04チャットと管理用チャネルの分離
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-realtime-socket-ioインストール後、Claude Code で/web-realtime-socket-ioを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/authentication.mdcore.mdrooms.md詳細
Socket.IO のリアルタイム通信パターン
クイックガイド: Socket.IO は WebSocket の上に構築されたプロトコルであり、WebSocket の実装ではありません。そのため、Socket.IO クライアントと通常の WebSocket サーバーは、どちらの方向でも通信できません。このレイヤーによって、約 14.5KB(gzip 圧縮時)で、トランスポートのフォールバック、自動再接続、ルーム、ネームスペース、確認応答が利用できます。判断を左右する重要なポイントは、
authは再接続のたびに再実行される関数を受け取れること、timeoutとackTimeoutは別々のタイマーであること、そしてsocket.recovered(v4.6.0+)によって、未受信イベントが再送されたか、完全な状態の再取得が必要かを確認できることです。
詳細なリソース:
- examples/core.md — 型付きソケットファクトリー、接続とイベントのフック、確認応答付き emit、オフラインキュー、揮発性イベント、Manager の多重化
- examples/authentication.md — トークン認証、再接続時の更新、ネームスペースごとの認証、Cookie 認証、認証状態マシン
- examples/rooms.md — ルームマネージャーとフック、複数ルームのチャット、ネームスペースソケット、条件付きネームスペースアクセス
- reference.md — クライアントオプション、ソケットと Manager のイベント、切断理由、比較表、チェックリスト
どの方法が適用されるか
- デフォルトのネームスペースへの単一接続 —
io(url, options)はソケットとその Manager を同時に作成します。examples/core.md に記載されている方法で、ほとんどのアプリに適しています。 - 1 つの接続で複数のネームスペースを使用する —
new Manager(url)を自分で構築し、ネームスペースごとにmanager.socket("/chat")を呼び出します。ネームスペースは単一のトランスポートを共有し、それぞれ独自のauthを設定できるため、ネームスペースごとの認可が可能になります。examples/rooms.md を参照してください。
同じ URL に対して io() を複数回呼び出すと、多重化されるのではなく、2 つ目のトランスポートが開きます。1 つの接続を共有するのは Manager です。
<critical_requirements>
Socket.IO のコードを書く前に
ServerToClientEvents と ClientToServerEvents を宣言し、ソケットにそれらの型を指定してください。 イベント名は実行時には文字列なので、これによって、呼び出されないままになるリスナーの "mesage" がコンパイルエラーになります。
トークンは auth オプションを通じて渡してください。 トークンは URL ではなくハンドシェイクで送信されるため、サーバーログ、ブラウザーの履歴、プロキシログに残りません。また、関数形式の auth は再接続のたびに再評価されるため、更新されたトークンが古くならずに済みます。
追加したリスナーは、同じ関数参照を使ってすべて削除してください。 useEffect のクリーンアップで socket.off(event, handler) を呼び出すことで、再レンダリングのたびにハンドラーが積み重なり、マウントごとに同じメッセージを処理してしまうのを防げます。
connect_error と、manager の reconnect_failed を処理してください。 これらを処理することで、ユーザーが画面の更新が単に止まったと感じる原因となる、2 種類の障害に対処できます。
connect の後に socket.recovered を確認してください(v4.6.0+)。 これは、サーバーが未受信のイベントを再送したか、それともクライアントが状態全体を更新する必要があるかを示します。
</critical_requirements>
自動検出: socket.io-client、io()、Manager、manager.socket()、socket.emit、socket.on、socket.off、emitWithAck、socket.timeout()、ackTimeout、socket.volatile、socket.recovered、socket.active、connect_error、reconnect_attempt、reconnect_failed、ServerToClientEvents、ClientToServerEvents、autoConnect、reconnectionDelayMax
対象:
- 配信確認が必要な双方向メッセージング
- 特定の対象へのブロードキャストに使うルームと名前空間
- 認証情報の更新と接続状態の復旧を伴う再接続
- ポーリングへのフォールバックが必要な、制約の厳しいネットワーク
- クライアントとサーバー間の型付きイベント契約
別の箇所で扱う内容:
- トークンの取得元と更新方法 — ソケットは
authが返す値をそのまま送信します。 - 受信データの保存方法と描画方法 — ソケットフックはメッセージと接続状態を返します。それ以上のことは、このフックの役割ではありません。
- サーバー側のルームへの参加、名前空間のミドルウェア、接続状態の復旧設定。ここではクライアント側を扱い、サーバー側で有効にする必要がある機能を示します。
- ルーム、確認応答、フォールバックを使わない双方向チャネル — それはこのプロトコルレイヤーがラップしているネイティブの WebSocket API であり、このレイヤーが公開するものではありません。
Socket.IO は、手書きで実装する必要のある次の 4 つの機能と引き換えに、バンドルサイズとプロトコル互換性を犠牲にします。トランスポートのフォールバック、再接続、サーバー側のグループ化、確認応答です。
- トランスポートは抽象化されています。 クライアントは HTTP ロングポーリングで接続を開始し、WebSocket が利用可能になるとアップグレードするため、アップグレードをブロックするネットワークでも、失敗せずに機能が低下する形で動作します。
- ルームはサーバー側の概念です。 クライアントは参加を要求し、サーバーがその可否を決定します。クライアントに自分が参加しているルームが通知されることはありません。
- 名前空間はプロトコルレベルの機能です。 クライアントは
/chatや/adminに明示的に接続します。それぞれに独自のミドルウェアとauthがあり、トランスポートはすべてで共有されます。 - 接続状態の復旧(v4.6.0+) では、サーバーで設定した期間内に限り、短時間の切断中に受信できなかったイベントが再送されます。既定の期間は 2 分です。
CONNECTING -> CONNECTED <-> (events) -> DISCONNECTING -> DISCONNECTED
| |
(error) <- reconnect <- (disconnect)
<decision_framework>
名前空間かルームか
What is being separated?
+-- A distinct feature area, with its own auth or middleware?
| -> namespace — the client connects to it, e.g. /chat, /admin
+-- A set of users inside one feature, for targeted broadcast?
| -> room — server-side only, joined on request
+-- Neither — one channel for everything?
-> the default namespace "/"
認証方法
auth オプションはトークンの受け渡しに使えます。また、その関数形式を使うことで、再接続時にもトークンを最新の状態に保てます。セッション Cookie を使う場合は withCredentials: true を設定し、auth は指定しないでください。この方法を機能させるには、サーバーの CORS 設定で認証情報を許可する必要があります。特定の名前空間に高い権限が必要な場合は、アプリケーションコードで制御するのではなく、manager.socket("/admin", { auth }) でその名前空間専用の auth を設定してください。
どの配信保証を選ぶか
通常の emit は送信して終わりです。送信側が相手に届いたことを確認する必要がある場合は、1 回の呼び出しには socket.timeout(ms).emitWithAck(...) を使います。または、ackTimeout と retries(v4.6.0+)を設定すると、クライアントが自動で再送します。この場合、同じパケットが 2 回届く可能性があるため、サーバーのハンドラーは冪等性を担保する必要があります。次の更新で置き換わるデータ(カーソル位置やプレゼンスの通知など)には、キューに入れるのではなく破棄する socket.volatile.emit() を使います。
バイナリの送信
バイナリのペイロードはイベントに直接含めることができます。プロトコルは、JSON と混在したオブジェクト内も含め、ArrayBuffer、Buffer、Blob をシリアライズします。大きなファイルは自分でチャンクに分割してください。まずメタデータを送り、その後に確認応答付きのチャンクを送信すれば、進捗を報告でき、失敗時には再開できます。
</decision_framework>
基本パターン
パターン 1:型付きイベントインターフェイス
v4 では、コンパイル時に双方向の型が強制されます。
interface ServerToClientEvents {
"message:received": (message: ChatMessage) => void;
error: (error: SocketError) => void;
}
interface ClientToServerEvents {
"message:send": (content: string, ack: (res: SendResult) => void) => void;
}
type TypedSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
コード全文:examples/core.md
パターン 2:クライアントの設定
timeout は接続タイムアウトで、デフォルトは 20000ms です。ackTimeout は emit ごとの確認応答タイムアウトであり、併せて retries が必要です。名前は似ていますが、この 2 つは無関係です。
const socket: TypedSocket = io(url, {
auth: { token },
reconnectionAttempts: MAX_RECONNECTION_ATTEMPTS,
reconnectionDelay: RECONNECTION_DELAY_MS,
timeout: CONNECTION_TIMEOUT_MS,
transports: ["websocket", "polling"],
});
コード全文:examples/core.md
パターン 3:接続のライフサイクル
ソケットレベルのイベントはソケットについて示し、マネージャーレベルのイベント(socket.io)は基盤となる接続について示します。再接続の進行状況が通知されるのは、後者だけです。
socket.on("connect", () => {
if (!socket.recovered) refetchState();
});
socket.on("disconnect", (reason) => {
// socket.active === true means a reconnect is already scheduled
});
socket.on("connect_error", (error) => showError(error));
socket.io.on("reconnect_attempt", (attempt) => showReconnecting(attempt));
socket.io.on("reconnect_failed", () => showPermanentFailure());
コード全文:examples/core.md
パターン 4:確認応答
形式は 2 つですが、保証される内容は同じです。emitWithAck は 1 回の応答を待ち、ackTimeout と retries は自動で再送します。
const response = await socket
.timeout(ACK_TIMEOUT_MS)
.emitWithAck("message:send", content);
// or: let the client retry, and make the server handler idempotent
const socket = io(url, { ackTimeout: ACK_TIMEOUT_MS, retries: MAX_RETRIES });
socket.emit("message:send", content, (response) => confirm(response));
コード全文:examples/core.md
パターン 5:認証トークンの処理
auth オブジェクトは、生成時に 1 回だけ読み取られます。auth 関数は、再接続を含む接続試行のたびに呼び出されます。この違いによって、トークン更新後もセッションが維持されるかどうかが決まります。
const socket = io(url, {
auth: (cb) => {
cb({ token: getToken() });
},
});
// or update it just before the retry
socket.io.on("reconnect_attempt", () => {
socket.auth = { token: getToken() };
});
コード全文:examples/authentication.md
パターン 6:1 つの接続上で複数の名前空間を使う
Manager がトランスポートを管理し、各名前空間のソケットはそのトランスポートを利用します。また、それぞれ独自の認証情報を持つことができます。
const manager = new Manager(url, { autoConnect: false });
const chatSocket = manager.socket("/chat");
const adminSocket = manager.socket("/admin", { auth: { token: adminToken } });
manager.connect();
コード全文:examples/rooms.md
パターン 7:リスナーのクリーンアップ
off は関数参照で照合するため、ハンドラーはインラインのアロー関数ではなく、安定したバインディングにする必要があります。
useEffect(() => {
const handler = (msg: Message) => setMessages((prev) => [...prev, msg]);
socket.on("message", handler);
return () => {
socket.off("message", handler);
};
}, [socket]);
コード全文:examples/core.md
<red_flags>
注意すべき点
実行時に問題が発生します:
- 接続 URL のクエリ文字列にトークンを入れると、サーバーログ、ブラウザーの履歴、その間にあるすべてのプロキシに残ります。
authに入れてください。 - 対応する
offがないsocket.on(...)は、マウントのたびにハンドラーが蓄積され、蓄積された回数分、各メッセージが処理されます。 - インラインのアロー関数を
offに渡すと、登録時のものとは異なる参照になるため、何も削除されません。 - 切断中のソケットで
socket.emit()を呼び出すと、通知なく破棄されます。socket.connectedを確認するか、キューに入れてください。 socket.idをユーザー識別子として使うと、再接続のたびに再生成されます。サーバーが発行したユーザー ID をキーにしてください。- 長時間のセッションで静的な
authオブジェクトを使うと、トークンの期限が切れ、再接続がすべて拒否されます。関数形式を使ってください。 retriesを設定せずにackTimeoutを設定しても、それが属する再送動作は有効になりません。
予想外の動作:
- プロトコルは、どちらの方向でも通常の WebSocket クライアントとの互換性がありません。このライブラリでは Socket.IO 以外のサーバーに接続できません。
- デフォルトのトランスポート順序は、最初にポーリング、その後にアップグレードです。最初からソケットではありません。
socket.recoveredがtrueになるのは、サーバーで接続状態の復元が有効になっている場合に限られます。- 名前空間は同じトランスポートを共有するため、トランスポートに障害が発生すると、すべての名前空間が同時に停止します。
- ルームはクライアントから見えません。所属しているルームを報告するクライアント API はありません。
volatile.emit()は混雑時に意図的にメッセージを破棄します。必ず届ける必要があるメッセージには使わないでください。retriesを有効にすると、同じパケットが複数回配信されることがあるため、サーバー側のハンドラーは冪等である必要があります。