本文へ移動
cccskills
無料GitHub で公開

websocket-patterns

Connection management, room patterns, reconnection strategies, message buffering, and binary protocol design.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md6.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

WebSocket Patterns

Production WebSocket patterns for real-time applications.

Connection Management

import { WebSocketServer, WebSocket } from 'ws'

interface Client {
  id: string
  ws: WebSocket
  rooms: Set<string>
  lastPing: number
  metadata: Record<string, unknown>
}

class ConnectionManager {
  private clients = new Map<string, Client>()
  private heartbeatInterval: NodeJS.Timeout

  constructor(private wss: WebSocketServer) {
    this.heartbeatInterval = setInterval(() => this.checkHeartbeats(), 30_000)

    wss.on('connection', (ws, req) => {
      const clientId = crypto.randomUUID()
      const client: Client = {
        id: clientId,
        ws,
        rooms: new Set(),
        lastPing: Date.now(),
        metadata: { ip: req.socket.remoteAddress }
      }
      this.clients.set(clientId, client)

      ws.on('pong', () => { client.lastPing = Date.now() })
      ws.on('close', () => this.removeClient(clientId))
      ws.on('error', () => this.removeClient(clientId))

      this.send(client, { type: 'connected', clientId })
    })
  }

  private checkHeartbeats(): void {
    const staleThreshold = Date.now() - 45_000
    for (const [id, client] of this.clients) {
      if (client.lastPing < staleThreshold) {
        client.ws.terminate()
        this.removeClient(id)
      } else {
        client.ws.ping()
      }
    }
  }

  private removeClient(id: string): void {
    const client = this.clients.get(id)
    if (!client) return
    for (const room of client.rooms) {
      this.leaveRoom(id, room)
    }
    this.clients.delete(id)
  }

  send(client: Client, data: unknown): void {
    if (client.ws.readyState === WebSocket.OPEN) {
      client.ws.send(JSON.stringify(data))
    }
  }

  destroy(): void {
    clearInterval(this.heartbeatInterval)
  }
}

Room Pattern

class RoomManager {
  private rooms = new Map<string, Set<string>>()

  joinRoom(clientId: string, room: string): void {
    if (!this.rooms.has(room)) {
      this.rooms.set(room, new Set())
    }
    this.rooms.get(room)!.add(clientId)
  }

  leaveRoom(clientId: string, room: string): void {
    const members = this.rooms.get(room)
    if (!members) return
    members.delete(clientId)
    if (members.size === 0) this.rooms.delete(room)
  }

  broadcast(room: string, data: unknown, excludeId?: string): void {
    const members = this.rooms.get(room)
    if (!members) return
    const payload = JSON.stringify(data)
    for (const id of members) {
      if (id === excludeId) continue
      const client = this.clients.get(id)
      if (client?.ws.readyState === WebSocket.OPEN) {
        client.ws.send(payload)
      }
    }
  }

  getRoomSize(room: string): number {
    return this.rooms.get(room)?.size ?? 0
  }
}

Client-Side Reconnection

class ReconnectingWebSocket {
  private ws: WebSocket | null = null
  private reconnectAttempts = 0
  private maxReconnectDelay = 30_000
  private messageBuffer: unknown[] = []
  private handlers = new Map<string, Function[]>()

  constructor(private url: string) {
    this.connect()
  }

  private connect(): void {
    this.ws = new WebSocket(this.url)

    this.ws.onopen = () => {
      this.reconnectAttempts = 0
      this.flushBuffer()
      this.emit('connected')
    }

    this.ws.onclose = (event) => {
      if (event.code === 1000) return  // Normal closure, no reconnect
      this.scheduleReconnect()
    }

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data as string)
      this.emit(data.type, data)
    }

    this.ws.onerror = () => {
      this.ws?.close()
    }
  }

  private scheduleReconnect(): void {
    // Exponential backoff with jitter
    const baseDelay = Math.min(1000 * 2 ** this.reconnectAttempts, this.maxReconnectDelay)
    const jitter = baseDelay * 0.3 * Math.random()
    const delay = baseDelay + jitter

    this.reconnectAttempts++
    setTimeout(() => this.connect(), delay)
  }

  send(data: unknown): void {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
    } else {
      // Buffer messages while disconnected (max 100)
      if (this.messageBuffer.length < 100) {
        this.messageBuffer.push(data)
      }
    }
  }

  private flushBuffer(): void {
    const buffered = [...this.messageBuffer]
    this.messageBuffer = []
    for (const msg of buffered) {
      this.send(msg)
    }
  }

  on(event: string, handler: Function): void {
    if (!this.handlers.has(event)) this.handlers.set(event, [])
    this.handlers.get(event)!.push(handler)
  }

  private emit(event: string, ...args: unknown[]): void {
    for (const handler of this.handlers.get(event) ?? []) {
      handler(...args)
    }
  }
}

Message Protocol Design

// Typed message envelope
interface WsMessage<T = unknown> {
  type: string
  id: string          // For request-response correlation
  timestamp: number
  payload: T
}

// Request-response over WebSocket
class WsRpc {
  private pending = new Map<string, { resolve: Function; timer: NodeJS.Timeout }>()

  async request<T>(type: string, payload: unknown, timeoutMs = 5000): Promise<T> {
    const id = crypto.randomUUID()
    const msg: WsMessage = { type, id, timestamp: Date.now(), payload }

    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        this.pending.delete(id)
        reject(new Error(`RPC timeout: ${type}`))
      }, timeoutMs)

      this.pending.set(id, { resolve, timer })
      this.ws.send(JSON.stringify(msg))
    })
  }

  handleResponse(msg: WsMessage): void {
    const entry = this.pending.get(msg.id)
    if (!entry) return
    clearTimeout(entry.timer)
    this.pending.delete(msg.id)
    entry.resolve(msg.payload)
  }
}

Checklist

  • Heartbeat/ping-pong every 30s, terminate stale connections at 45s
  • Exponential backoff with jitter for client reconnection (1s-30s)
  • Message buffer during disconnection (cap at 100 messages)
  • Auth token validation on initial handshake (not in messages)
  • Room-based broadcasting for scoped message delivery
  • Rate limit per client (max messages/second)
  • Message size limit (max 1MB per frame)
  • Graceful shutdown: close code 1001, drain messages before exit

Anti-Patterns

  • No heartbeat: zombie connections consuming resources
  • Reconnecting without backoff: hammering server on outage
  • Unbounded message buffer: memory leak during long disconnections
  • Auth in every message: use connection-level auth, not per-message
  • Broadcasting to all clients when only a room subset needs the update
  • Synchronous JSON.parse in hot path: use worker threads for heavy payloads

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

WCAG 2.2 AA compliance, ARIA patterns, keyboard navigation, screen reader optimization

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

axe-core integration, WCAG 2.2 AA checklist, keyboard navigation testing, screen reader testing, and ARIA pattern validation.

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

Steam-style achievement system with XP, levels, streaks, and skill trees. Gamifies the development workflow. 25 achievements across 5 categories.

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

Framework for measuring and tracking agent response quality over time. Detects regressions before they reach production. Use when evaluating agent changes, auditing quality, or establishing performance baselines.

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

Agent Context Isolation

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

Agent ve skill dosyalarinin yapisal dogrulamasi. Frontmatter kontrol, naming convention, zorunlu bolum kontrolu, tutarlilik denetimi. Yeni agent/skill eklendiginde veya mevcut dosyalar duzenlediginde otomatik calistirilir.

日本語の概要は準備中です。原文の説明を表示しています。

vibeeval/vibecosystem5332026年8月9日 更新

vibeeval のスキルをすべて見る

このスキルの問題を報告する