Gryt

Multi-Server

Server switching and room isolation

Gryt supports connecting to multiple servers simultaneously with seamless switching and isolated voice channels.

Key Features

Server Discovery

  • Automatic Detection: Automatic server detection and connection
  • Manual Addition: Add custom servers through the interface
  • Server Validation: Verify server availability and compatibility
  • Connection Testing: Test connectivity before joining

Seamless Server Switching

  • No Disconnection: Switch between servers without losing voice connection
  • State Preservation: Maintain user state across server switches
  • Background Connection: Keep connections alive to multiple servers
  • Smart Routing: Intelligent connection management

Room Isolation

  • Unique Room IDs: Server-prefixed room IDs prevent cross-server interference
  • Independent Channels: Each server maintains its own voice channels
  • User State Separation: User states are isolated per server
  • Resource Management: Efficient resource allocation per server

Connection State Management

  • Robust Handling: Handles network changes and reconnections
  • Automatic Recovery: Automatic reconnection with exponential backoff
  • State Synchronization: Real-time state updates across all servers
  • Error Handling: Graceful handling of connection failures

Architecture Overview

The multi-server architecture consists of several key components:

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Server A      │    │   Server B      │    │   Server C      │
│                 │    │                 │    │                 │
│ • Room: tech_1  │    │ • Room: game_1  │    │ • Room: music_1 │
│ • Users: 5      │    │ • Users: 12     │    │ • Users: 3      │
│ • Status: ✅    │    │ • Status: ✅    │    │ • Status: ⚠️    │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         └───────────────────────┼───────────────────────┘

                    ┌─────────────────┐
                    │   Web Client    │
                    │                 │
                    │ • Multi-Server  │
                    │   Management    │
                    │ • State Sync    │
                    │ • Connection    │
                    │   Pool          │
                    └─────────────────┘

Server Management

Server Discovery

Automatic and manual server discovery:

// Server discovery interface
interface ServerInfo {
  id: string;
  name: string;
  url: string;
  icon?: string;
  status: 'online' | 'offline' | 'connecting' | 'error';
  users: number;
  channels: Channel[];
  lastSeen: Date;
}

// Server discovery methods
const serverDiscovery = {
  // Automatic discovery via mDNS/Bonjour
  discoverLocalServers: async (): Promise<ServerInfo[]> => {
    // Implementation for local network discovery
    return await mDNSDiscovery();
  },
  
  // Manual server addition
  addCustomServer: async (url: string): Promise<ServerInfo> => {
    const serverInfo = await validateServer(url);
    if (serverInfo) {
      await saveServer(serverInfo);
      return serverInfo;
    }
    throw new Error('Invalid server URL');
  },
  
  // Server validation
  validateServer: async (url: string): Promise<boolean> => {
    try {
      const response = await fetch(`${url}/health`);
      return response.ok;
    } catch {
      return false;
    }
  }
};

Server Connection Management

// Multi-server connection manager
class MultiServerManager {
  private servers: Map<string, ServerConnection> = new Map();
  private currentServer: string | null = null;
  
  // Connect to a server
  async connectToServer(serverId: string): Promise<void> {
    const server = this.servers.get(serverId);
    if (!server) {
      throw new Error('Server not found');
    }
    
    // Establish WebSocket connection
    await server.connect();
    
    // Update connection state
    this.updateServerStatus(serverId, 'connected');
  }
  
  // Switch to a different server
  async switchServer(serverId: string): Promise<void> {
    if (this.currentServer === serverId) {
      return; // Already connected to this server
    }
    
    // Disconnect from current server (but keep connection alive)
    if (this.currentServer) {
      await this.disconnectFromCurrentServer();
    }
    
    // Connect to new server
    await this.connectToServer(serverId);
    this.currentServer = serverId;
    
    // Notify UI of server switch
    this.emit('server-switched', serverId);
  }
  
  // Keep connections alive to all servers
  private keepConnectionsAlive(): void {
    this.servers.forEach(server => {
      if (server.status === 'connected') {
        server.ping();
      }
    });
  }
}

Room Isolation

Unique Room ID Generation

Each server maintains its own room namespace:

// Room ID generation with server prefixing
const generateRoomId = (serverName: string, channelId: string): string => {
  // Extract server prefix (e.g., "techial" from "techial.example.com")
  const prefix = serverName.split('.')[0].toLowerCase();
  
  // Generate unique room ID
  const roomId = `${prefix}_${channelId}`;
  
  return roomId;
};

// Example room IDs
const roomIds = {
  'techial.example.com': {
    'general': 'techial_general',
    'voice': 'techial_voice',
    'gaming': 'techial_gaming'
  },
  'gryta.example.com': {
    'general': 'gryta_general',
    'voice': 'gryta_voice',
    'music': 'gryta_music'
  }
};

Room State Management

// Room state management per server
interface RoomState {
  serverId: string;
  roomId: string;
  users: Map<string, UserState>;
  isActive: boolean;
  lastActivity: Date;
}

// Multi-server room manager
class MultiServerRoomManager {
  private rooms: Map<string, RoomState> = new Map();
  
  // Join a room on a specific server
  async joinRoom(serverId: string, channelId: string): Promise<void> {
    const server = this.getServer(serverId);
    const roomId = generateRoomId(server.name, channelId);
    
    // Create room state if it doesn't exist
    if (!this.rooms.has(roomId)) {
      this.rooms.set(roomId, {
        serverId,
        roomId,
        users: new Map(),
        isActive: false,
        lastActivity: new Date()
      });
    }
    
    // Join the room
    await server.joinRoom(roomId);
    
    // Update room state
    const room = this.rooms.get(roomId)!;
    room.isActive = true;
    room.lastActivity = new Date();
  }
  
  // Leave a room
  async leaveRoom(serverId: string, channelId: string): Promise<void> {
    const server = this.getServer(serverId);
    const roomId = generateRoomId(server.name, channelId);
    
    // Leave the room
    await server.leaveRoom(roomId);
    
    // Update room state
    const room = this.rooms.get(roomId);
    if (room) {
      room.isActive = false;
      room.lastActivity = new Date();
    }
  }
}

Seamless Server Switching

State Preservation

Maintain user state across server switches:

// User state preservation
interface UserState {
  id: string;
  nickname: string;
  isMuted: boolean;
  isDeafened: boolean;
  microphoneVolume: number;
  outputVolume: number;
  audioDevice: string;
  preferences: UserPreferences;
}

// State manager for multi-server
class MultiServerStateManager {
  private globalState: UserState;
  private serverStates: Map<string, Partial<UserState>> = new Map();
  
  // Save state before server switch
  saveStateForServer(serverId: string): void {
    this.serverStates.set(serverId, {
      isMuted: this.globalState.isMuted,
      microphoneVolume: this.globalState.microphoneVolume,
      outputVolume: this.globalState.outputVolume
    });
  }
  
  // Restore state after server switch
  restoreStateForServer(serverId: string): void {
    const serverState = this.serverStates.get(serverId);
    if (serverState) {
      // Restore server-specific state
      Object.assign(this.globalState, serverState);
      
      // Apply state to audio pipeline
      this.applyAudioState(this.globalState);
    }
  }
  
  // Apply audio state
  private applyAudioState(state: UserState): void {
    // Update audio pipeline
    updateAudioPipeline({
      muted: state.isMuted,
      microphoneVolume: state.microphoneVolume,
      outputVolume: state.outputVolume
    });
  }
}

Background Connection Management

// Background connection manager
class BackgroundConnectionManager {
  private connections: Map<string, WebSocket> = new Map();
  private keepAliveInterval: NodeJS.Timeout;
  
  // Maintain background connections
  maintainConnections(): void {
    this.keepAliveInterval = setInterval(() => {
      this.connections.forEach((ws, serverId) => {
        if (ws.readyState === WebSocket.OPEN) {
          // Send ping to keep connection alive
          ws.send(JSON.stringify({ type: 'ping' }));
        } else if (ws.readyState === WebSocket.CLOSED) {
          // Attempt to reconnect
          this.reconnect(serverId);
        }
      });
    }, 30000); // Ping every 30 seconds
  }
  
  // Reconnect to a server
  private async reconnect(serverId: string): Promise<void> {
    try {
      const server = this.getServer(serverId);
      const ws = new WebSocket(server.url);
      
      ws.onopen = () => {
        this.connections.set(serverId, ws);
        this.emit('server-reconnected', serverId);
      };
      
      ws.onclose = () => {
        this.connections.delete(serverId);
        this.emit('server-disconnected', serverId);
      };
    } catch (error) {
      console.error(`Failed to reconnect to server ${serverId}:`, error);
    }
  }
}

Connection State Management

Robust Error Handling

// Connection state management
interface ConnectionState {
  status: 'connecting' | 'connected' | 'disconnected' | 'error';
  lastError?: string;
  reconnectAttempts: number;
  maxReconnectAttempts: number;
  reconnectDelay: number;
}

// Connection manager with exponential backoff
class ConnectionManager {
  private states: Map<string, ConnectionState> = new Map();
  
  // Handle connection errors
  handleConnectionError(serverId: string, error: Error): void {
    const state = this.states.get(serverId) || this.createDefaultState();
    
    state.status = 'error';
    state.lastError = error.message;
    state.reconnectAttempts++;
    
    this.states.set(serverId, state);
    
    // Attempt reconnection with exponential backoff
    if (state.reconnectAttempts < state.maxReconnectAttempts) {
      const delay = Math.min(1000 * Math.pow(2, state.reconnectAttempts), 30000);
      setTimeout(() => this.reconnect(serverId), delay);
    }
  }
  
  // Reconnect with exponential backoff
  private async reconnect(serverId: string): Promise<void> {
    const state = this.states.get(serverId)!;
    
    try {
      state.status = 'connecting';
      await this.connectToServer(serverId);
      
      // Reset state on successful connection
      state.status = 'connected';
      state.reconnectAttempts = 0;
      state.lastError = undefined;
    } catch (error) {
      this.handleConnectionError(serverId, error as Error);
    }
  }
}

State Synchronization

// State synchronization across servers
class StateSynchronizer {
  private syncQueue: Map<string, any[]> = new Map();
  
  // Synchronize state across all connected servers
  async syncState(state: Partial<UserState>): Promise<void> {
    const promises = Array.from(this.connectedServers).map(serverId => 
      this.syncStateToServer(serverId, state)
    );
    
    await Promise.allSettled(promises);
  }
  
  // Sync state to a specific server
  private async syncStateToServer(serverId: string, state: Partial<UserState>): Promise<void> {
    try {
      const server = this.getServer(serverId);
      await server.updateUserState(state);
    } catch (error) {
      // Queue for retry
      this.queueForRetry(serverId, state);
    }
  }
  
  // Queue failed syncs for retry
  private queueForRetry(serverId: string, state: Partial<UserState>): void {
    if (!this.syncQueue.has(serverId)) {
      this.syncQueue.set(serverId, []);
    }
    
    this.syncQueue.get(serverId)!.push(state);
  }
}

User Interface

Server List Management

// Server list UI component
const ServerList = () => {
  const [servers, setServers] = useState<ServerInfo[]>([]);
  const [currentServer, setCurrentServer] = useState<string | null>(null);
  
  // Add new server
  const addServer = async (url: string) => {
    try {
      const serverInfo = await serverDiscovery.addCustomServer(url);
      setServers(prev => [...prev, serverInfo]);
    } catch (error) {
      showError('Failed to add server: ' + error.message);
    }
  };
  
  // Switch to server
  const switchToServer = async (serverId: string) => {
    try {
      await multiServerManager.switchServer(serverId);
      setCurrentServer(serverId);
    } catch (error) {
      showError('Failed to switch server: ' + error.message);
    }
  };
  
  return (
    <div className="server-list">
      {servers.map(server => (
        <ServerItem
          key={server.id}
          server={server}
          isActive={currentServer === server.id}
          onSwitch={() => switchToServer(server.id)}
          onRemove={() => removeServer(server.id)}
        />
      ))}
      <AddServerButton onAdd={addServer} />
    </div>
  );
};

Server Status Indicators

// Server status indicator component
const ServerStatusIndicator = ({ server }: { server: ServerInfo }) => {
  const getStatusColor = (status: string) => {
    switch (status) {
      case 'online': return 'green';
      case 'offline': return 'red';
      case 'connecting': return 'yellow';
      case 'error': return 'orange';
      default: return 'gray';
    }
  };
  
  const getStatusIcon = (status: string) => {
    switch (status) {
      case 'online': return '✅';
      case 'offline': return '❌';
      case 'connecting': return '🔄';
      case 'error': return '⚠️';
      default: return '❓';
    }
  };
  
  return (
    <div className="server-status">
      <span className="status-icon">{getStatusIcon(server.status)}</span>
      <span className="status-text" style={{ color: getStatusColor(server.status) }}>
        {server.status}
      </span>
      <span className="user-count">{server.users} users</span>
    </div>
  );
};

Troubleshooting

Common Multi-Server Issues

Server connection failures?

# Check server availability
curl -f https://server.example.com/health

# Verify WebSocket connectivity
wscat -c wss://server.example.com/ws

# Check firewall settings
sudo ufw status

Room isolation problems?

# Verify room ID generation
# Check server name prefixing
# Test with different servers
# Verify channel isolation

State synchronization issues?

# Check WebSocket connections
# Verify state persistence
# Test server switching
# Check error logs

Performance issues with multiple servers?

# Monitor connection count
# Check resource usage
# Optimize connection pooling
# Reduce background connections

Debug Tools

Enable multi-server debugging:

// Enable multi-server debug mode
localStorage.setItem('debug', 'gryt:multiserver:*');

// Multi-server metrics
const multiServerMetrics = {
  connectedServers: getConnectedServerCount(),
  activeConnections: getActiveConnectionCount(),
  roomCount: getTotalRoomCount(),
  stateSyncQueue: getStateSyncQueueSize()
};

console.log('Multi-Server Metrics:', multiServerMetrics);

Performance Metrics

Multi-Server Performance

Monitor multi-server efficiency:

  • Connection Latency: < 100ms per server
  • State Sync Time: < 50ms across all servers
  • Memory Usage: < 100MB for 10 servers
  • CPU Usage: < 10% for connection management

Scalability Metrics

Measure multi-server scalability:

  • Max Servers: 50+ concurrent servers
  • Max Rooms: 1000+ rooms across all servers
  • Max Users: 10,000+ users across all servers
  • Connection Pool: 100+ concurrent connections

On this page