signal-fish-server 0.1.0

A lightweight, in-memory WebSocket signaling server for peer-to-peer game networking
Documentation
# Library Usage

Signal Fish Server is published as both a binary and a library crate. Embed the signaling server into your own
Rust application.

## Add Dependency

```toml
[dependencies]
signal-fish-server = "0.1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"

```

## Basic Embedded Server

```rust

use signal_fish_server::{
    config,
    database::DatabaseConfig,
    server::{EnhancedGameServer, ServerConfig},
    websocket,
};
use std::{net::SocketAddr, sync::Arc, time::Duration};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load configuration
    let cfg = config::load();

    // Build server configuration
    // Note: Only key fields shown for brevity - see src/server.rs ServerConfig for all required fields
    let server_config = ServerConfig {
        default_max_players: cfg.server.default_max_players,
        ping_timeout: Duration::from_secs(cfg.server.ping_timeout),
        room_cleanup_interval: Duration::from_secs(cfg.server.room_cleanup_interval),
        max_rooms_per_game: cfg.server.max_rooms_per_game,
        rate_limit_config: cfg.server.rate_limit_config.clone(),
        empty_room_timeout: Duration::from_secs(cfg.server.empty_room_timeout),
        inactive_room_timeout: Duration::from_secs(cfg.server.inactive_room_timeout),
        max_message_size: cfg.server.max_message_size,
        max_connections_per_ip: cfg.server.max_connections_per_ip,
        require_metrics_auth: cfg.server.require_metrics_auth,
        metrics_auth_token: cfg.server.metrics_auth_token.clone(),
        reconnection_window: Duration::from_secs(cfg.server.reconnection_window),
        event_buffer_size: cfg.server.event_buffer_size,
        enable_reconnection: cfg.server.enable_reconnection,
        websocket_config: cfg.server.websocket_config.clone(),
        auth_enabled: cfg.server.auth_enabled,
        heartbeat_throttle: Duration::from_secs(cfg.server.heartbeat_throttle_secs),
        region_id: cfg.server.region_id.clone(),
        room_code_prefix: cfg.server.room_code_prefix.clone(),
    };

    // Create the game server
    let game_server = Arc::new(
        EnhancedGameServer::new(
            server_config,
            cfg.protocol.clone(),
            cfg.relay_types.clone(),
            DatabaseConfig::InMemory,
            cfg.metrics.clone(),
            cfg.auth.clone(),
            cfg.coordination.clone(),
            cfg.security.transport.clone(),
            cfg.security.authorized_apps.clone(),
        )
        .await?
    );

    // Start background cleanup task
    let cleanup = game_server.clone();
    tokio::spawn(async move {
        cleanup.cleanup_task().await
    });

    // Build the Axum router
    let router = websocket::create_router(&cfg.security.cors_origins)
        .with_state(game_server);

    // Start listening
    let addr = SocketAddr::from(([0, 0, 0, 0], cfg.port));
    let listener = tokio::net::TcpListener::bind(addr).await?;

    println!("Server listening on {}", addr);

    axum::serve(
        listener,
        router.into_make_service_with_connect_info::<SocketAddr>(),
    )
    .await?;

    Ok(())
}

```

## Custom Storage Backend

Implement the `GameDatabase` trait for custom persistence:

```rust

use signal_fish_server::database::GameDatabase;
use signal_fish_server::protocol::{Room, RoomId, PlayerId, PlayerInfo, SpectatorInfo, ConnectionInfo, LobbyState};
use async_trait::async_trait;
use anyhow::Result;
use std::collections::HashMap;
use std::any::Any;
use uuid::Uuid;

pub struct MyCustomDatabase {
    // Your storage implementation (e.g., Redis client, PostgreSQL pool, etc.)
}

#[async_trait]
impl GameDatabase for MyCustomDatabase {
    async fn initialize(&self) -> Result<()> {
        // Initialize database connection and run migrations
        Ok(())
    }

    async fn create_room(
        &self,
        game_name: String,
        room_code: Option<String>,
        max_players: u8,
        supports_authority: bool,
        creator_id: PlayerId,
        relay_type: String,
        region_id: String,
        application_id: Option<Uuid>,
    ) -> Result<Room> {
        // Create and store room in your database
        todo!("Implement room creation")
    }

    async fn get_room(&self, game_name: &str, room_code: &str) -> Result<Option<Room>> {
        // Retrieve room from your database by game name and room code
        Ok(None)
    }

    async fn get_room_by_id(&self, room_id: &RoomId) -> Result<Option<Room>> {
        // Retrieve room from your database by ID
        Ok(None)
    }

    async fn add_player_to_room(&self, room_id: &RoomId, player: PlayerInfo) -> Result<bool> {
        // Add player to room (returns false if room is full)
        Ok(false)
    }

    async fn remove_player_from_room(
        &self,
        room_id: &RoomId,
        player_id: &PlayerId,
    ) -> Result<Option<PlayerInfo>> {
        // Remove player from room
        Ok(None)
    }

    async fn get_room_players(&self, room_id: &RoomId) -> Result<Vec<PlayerInfo>> {
        // Get all players in a room
        Ok(Vec::new())
    }

    async fn delete_room(&self, room_id: &RoomId) -> Result<bool> {
        // Delete a specific room by ID
        Ok(false)
    }

    async fn health_check(&self) -> bool {
        // Health check for your database
        true
    }

    fn as_any(&self) -> &(dyn Any + Send + Sync) {
        self
    }

    // ... implement all other required methods from the GameDatabase trait
    // See src/database/mod.rs for the complete trait definition
}

```

Use your custom database:

```rust

let game_server = EnhancedGameServer::new(
    server_config,
    protocol_config,
    relay_types_config,
    DatabaseConfig::Custom(Arc::new(MyCustomDatabase::new())),
    metrics_config,
    auth_config,
    coordination_config,
    transport_config,
    authorized_apps,
)
.await?;

```

## Custom Router Integration

Integrate Signal Fish into an existing Axum application:

```rust

use axum::{Router, routing::get};
use signal_fish_server::websocket;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Your existing routes
    let app = Router::new()
        .route("/", get(|| async { "Hello, world!" }))
        .route("/api/status", get(api_status));

    // Add Signal Fish routes
    let signal_fish_routes = websocket::create_router("*")
        .with_state(game_server);

    // Merge routers
    let app = app.merge(signal_fish_routes);

    // Start server
    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}

```

## Programmatic Configuration

Build configuration programmatically instead of from files:

```rust

use signal_fish_server::config::{
    Config, ServerConfig, ProtocolConfig, SecurityConfig,
};

let config = Config {
    port: 3536,
    server: ServerConfig {
        default_max_players: 8,
        ping_timeout: 30,
        room_cleanup_interval: 60,
        ..Default::default()
    },
    protocol: ProtocolConfig {
        max_game_name_length: 64,
        room_code_length: 6,
        ..Default::default()
    },
    security: SecurityConfig {
        cors_origins: "*".to_string(),
        require_websocket_auth: false,
        ..Default::default()
    },
    ..Default::default()
};

```

## Message Handling

> **Note:** The `EnhancedGameServer` does not currently expose public APIs for directly sending messages to
> players. The server automatically handles message routing based on client connections and room membership. This
> is an internal implementation detail.
>
> If you need to send custom server-initiated messages, you would need to extend the server implementation or use
> the internal message coordinator interfaces.

## Event Hooks

> **Note:** The `EnhancedGameServer` does not currently expose a public event subscription API. Server events are
> handled internally for metrics, logging, and coordination.
>
> If you need to monitor server events, consider:
>
> - Using the metrics endpoints (`/metrics` or `/metrics/prom`) to track room and player counts
> - Implementing custom logging by extending the server modules
> - Monitoring structured logs with a log aggregation system (all events are logged with `tracing`)

## Feature Flags

Enable optional features:

```toml
[dependencies]
signal-fish-server = { version = "0.1", features = ["tls", "legacy-fullmesh"] }

```

Available features:

- `tls` - Built-in TLS/mTLS support
- `legacy-fullmesh` - Upstream matchbox full-mesh signaling mode

## Testing

Use the library in your tests:

```rust
#[cfg(test)]
mod tests {
    use signal_fish_server::{
        server::EnhancedGameServer,
        protocol::{ClientMessage, ServerMessage},
    };

    #[tokio::test]
    async fn test_room_creation() {
        // Note: This is a conceptual example. The actual server does not expose
        // a public handle_message API. Message handling is internal to the WebSocket
        // connection lifecycle. For testing, see tests/integration_tests.rs for
        // examples of how to test via the WebSocket interface.

        let server = EnhancedGameServer::new(
            /* config */
        ).await.unwrap();

        // Example: Test via database state instead
        let room = server.database().get_room("test-game", "ABC123").await.unwrap();
        assert!(room.is_some());
    }
}

```

## API Documentation

Generate full API docs:

```bash

cargo doc --open --no-deps

```

## Next Steps

- [Protocol Reference]protocol.md - Message types and flow
- [Development]development.md - Building and testing