Claude-skill-registry dev-multiplayer-colyseus-client
Colyseus client SDK for React, connection methods, room events, and messaging. Use when connecting to multiplayer server.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dev-multiplayer-colyseus-client" ~/.claude/skills/majiayu000-claude-skill-registry-dev-multiplayer-colyseus-client && rm -rf "$T"
manifest:
skills/data/dev-multiplayer-colyseus-client/SKILL.mdsource content
Colyseus Client SDK
Connect to Colyseus server from React applications with colyseus.js.
When to Use
Use when:
- Connecting to game server from client
- Handling room state changes
- Sending/receiving messages
- Managing room lifecycle
Basic Connection
import { Client, Room } from 'colyseus.js'; const client = new Client('ws://localhost:2567'); async function joinGame() { try { const room = await client.joinOrCreate('game_room', {}); room.onStateChange((state) => { console.log('New state:', state); }); room.onMessage('game_event', (data) => { console.log('Received:', data); }); room.onLeave((code) => { console.log('Left room:', code); }); } catch (e) { console.error('Join error:', e); } }
Connection Methods
// Join existing or create new const room = await client.joinOrCreate('room_name', { options: 'value' }); // Create new (fails if exists) const room = await client.create('room_name', { options: 'value' }); // Join existing only (fails if full/not found) const room = await client.join('room_name', { options: 'value' }); // Join by specific room ID const room = await client.joinById('room_id_here', { options: 'value' }); // Reconnect to previous room const room = await client.reconnect(reconnectionToken); // Consume seat reservation const room = await client.consumeSeatReservation(reservation);
React Integration
import { useEffect, useRef, useState } from 'react'; import { Client, Room } from 'colyseus.js'; const client = new Client('ws://localhost:2567'); function GameRoom() { const roomRef = useRef<Room>(); const [isConnecting, setIsConnecting] = useState(true); const [players, setPlayers] = useState([]); useEffect(() => { const req = client.joinOrCreate('my_room', {}); req.then((room) => { roomRef.current = room; setIsConnecting(false); room.onStateChange((state) => setPlayers(state.players.toJSON())); }); return () => { req.then((room) => room.leave()); }; }, []); return ( <div> {isConnecting ? 'Connecting...' : players.map((player) => ( <div key={player.id}>{player.name}</div> )) } </div> ); }
Room Methods
// Properties room.state // Current room state (Schema instance) room.sessionId // Unique client identifier room.id // Room identifier (shareable) room.name // Room handler name // Send message to server room.send('message_type', { data: 'value' }); // Send raw bytes room.sendBytes(0, [0x01, 0x02, 0x03]); // Leave room room.leave(); // Consented leave room.leave(false); // Forced leave // Remove all listeners room.removeAllListeners();
Room Events
// Message events room.onMessage('message_type', (data) => { console.log('Received:', data); }); // State change events room.onStateChange((state) => { console.log('State updated:', state.toJSON()); }); // One-time state change (first state only) room.onStateChange.once((state) => { console.log('Initial state:', state); }); // Leave event room.onLeave((code) => { console.log('Left room with code:', code); // 1000 = normal shutdown // 4000-4999 = custom codes }); // Error event room.onError((code, message) => { console.error('Room error:', code, message); });
HTTP Requests
// GET request client.http.get('/api/endpoint').then(response => { console.log(response.data); }); // POST request client.http.post('/api/endpoint', { body: 'data' }).then(response => { console.log(response.data); }); // PUT request client.http.put('/api/endpoint', { body: 'data' }).then(response => { console.log(response.data); }); // DELETE request client.http.delete('/api/endpoint').then(response => { console.log(response.data); });
React Context Provider Pattern
// RoomContext.tsx import React, { createContext, useContext, useState, useEffect } from 'react'; import { Client, Room } from 'colyseus.js'; interface RoomContextType { room: Room | null; state: any; isConnected: boolean; join: () => void; } export const RoomContext = createContext<RoomContextType>({} as RoomContextType); export function useRoom() { return useContext(RoomContext); } export function RoomProvider({ children }: { children: React.ReactNode }) { const [room, setRoom] = useState<Room | null>(null); const [state, setState] = useState<any>(null); const [isConnected, setIsConnected] = useState(false); const join = async () => { try { const client = new Client('ws://localhost:2567'); const joinedRoom = await client.joinOrCreate('game_room'); setRoom(joinedRoom); setIsConnected(true); joinedRoom.onStateChange((newState) => { setState(newState.toJSON()); }); joinedRoom.onLeave(() => { setIsConnected(false); setRoom(null); }); } catch (e) { console.error('Failed to join:', e); } }; return ( <RoomContext.Provider value={{ room, state, isConnected, join }}> {children} </RoomContext.Provider> ); }
Authentication
// Anonymous sign-in client.auth.signInAnonymously() .then((response) => { console.log('Authenticated:', response.user); }) .catch((error) => { console.error('Auth error:', error); }); // Listen to auth changes client.auth.onChange((authData) => { if (authData.token) { console.log('User logged in:', authData.user); } else { console.log('User logged out'); } }); // Auth tokens sent automatically with HTTP requests client.http.get('/profile').then(response => { // Authorization header included });
Best Practices
- Leave room on unmount - Prevent memory leaks
- Handle errors - Network failures are common
- Use reconnection tokens - For seamless reconnection
- Send inputs, not positions - Server-authoritative
- Handle state changes - Use schema callbacks for updates
Connection Lifecycle Management
CRITICAL: Handle all connection states properly for a good UX.
// Connection states enum enum ConnectionState { DISCONNECTED = 'disconnected', CONNECTING = 'connecting', CONNECTED = 'connected', RECONNECTING = 'reconnecting', ERROR = 'error', } // Connection hook with lifecycle function useColyseusConnection(roomName: string) { const [state, setState] = useState<ConnectionState>(ConnectionState.DISCONNECTED); const [room, setRoom] = useState<Room | null>(null); const [error, setError] = useState<string | null>(null); const connect = async () => { setState(ConnectionState.CONNECTING); setError(null); try { const client = new Client('ws://localhost:2567'); const joinedRoom = await client.joinOrCreate(roomName); setRoom(joinedRoom); setState(ConnectionState.CONNECTED); // Handle disconnection joinedRoom.onLeave((code) => { if (code === 1000) { setState(ConnectionState.DISCONNECTED); } else { setState(ConnectionState.RECONNECTING); // Attempt reconnection setTimeout(() => connect(), 3000); } }); joinedRoom.onError((err) => { setError(err.message); setState(ConnectionState.ERROR); }); } catch (err) { setError(err.message); setState(ConnectionState.ERROR); } }; const disconnect = () => { if (room) { room.leave(); setRoom(null); setState(ConnectionState.DISCONNECTED); } }; return { state, error, connect, disconnect, room }; }
Reconnection Testing Pattern
For E2E tests covering disconnect/reconnect cycles:
// E2E test for reconnection test('handles disconnect and reconnect', async ({ page }) => { // 1. Connect to server await page.goto('/game'); await expect(page.getByTestId('connection-status')).toHaveText('Connected'); // 2. Simulate server disconnect await page.evaluate(() => { // @ts-ignore - Test utility window.__testDisconnectServer(); }); // 3. Verify disconnected state await expect(page.getByTestId('connection-status')).toHaveText('Disconnected'); // 4. Wait for auto-reconnect await expect(page.getByTestId('connection-status')).toHaveText('Reconnecting', { timeout: 5000 }); // 5. Verify reconnected await expect(page.getByTestId('connection-status')).toHaveText('Connected', { timeout: 10000 }); });
Connection State Monitoring
// Browser's online/offline events useEffect(() => { const handleOnline = () => { console.log('Browser online - attempt reconnect'); // Trigger reconnection logic }; const handleOffline = () => { console.log('Browser offline - show disconnected'); setState(ConnectionState.DISCONNECTED); }; window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []);
Common Mistakes
| ❌ Wrong | ✅ Right |
|---|---|
| Not leaving room on unmount | Always in cleanup |
| Using CommonJS imports | Use |
| Client sends absolute position | Client sends input (WASD, aim) |
| Ignoring error events | Always handle |