# HomesteadGateway Gateway between multiple HomesteadRelay's and the HomesteadToGo Bot. --- ## HomesteadGateway Developer Documentation ## Overview HomesteadGateway is a WebSocket-based message routing gateway that facilitates bidirectional communication between game server mods/plugins and external bots (e.g., Discord bots). It acts as a relay, routing messages based on channel identifiers and managing message queues when endpoints are offline. **Key Features:** - WebSocket-based real-time communication - Channel-based message routing - Automatic message queuing for offline recipients - API key authentication - Connection keep-alive via ping/pong - Support for multiple concurrent mod connections per channel --- ## Quick Start 1. Start the gateway (build/run your application that embeds the WebSocket gateway). 2. Connect a client to the WebSocket endpoint: - URL: `ws://:/sync?api_key=` 3. Immediately send a handshake JSON with type `mod` or `bot`. 4. On success, you’ll receive `{"status":"connected","type":"mod|bot"}`. 5. Exchange messages as JSON. See the full details below. --- ## Architecture ### Connection Types The gateway supports two types of connections: 1. **Mod Connection** - Game server mods/plugins that send and receive player messages 2. **Bot Connection** - External bots (typically Discord) that bridge messages to/from other platforms ### Message Flow ``` Mod (Server) ←→ Gateway ←→ Bot (Discord) ↓ ↓ Channel A Channel A ``` Messages are routed based on `channel_id`: - **Mod → Bot**: Messages from a mod are forwarded to the bot - **Bot → Mod**: Messages from a bot are forwarded to the mod registered for that channel --- ## Connection Setup ### Endpoint ``` ws://:/sync?api_key= ``` **Default Port:** 3333 (configurable) ### Authentication Authentication is performed via API key, which can be provided in two ways: 1. **Query Parameter** (recommended): ``` ws://localhost:3333/sync?api_key=gateway ``` 2. **HTTP Header**: ``` X-API-Key: gateway ``` ### Connection Timeout After connecting, you **must** send a handshake message within **60 seconds** or the connection will be closed. --- ## Handshake Protocol ### Step 1: Establish WebSocket Connection Connect to the `/sync` endpoint with your API key. ### Step 2: Send Handshake Message Immediately after connecting, send a JSON handshake message: ```json { "type": "mod", // or "bot" "data": { ... } } ``` #### Mod Handshake For game server mods/plugins: ```json { "type": "mod", "data": { "server_id": "minecraft-server-001", "channel_id": "123456789" } } ``` **Fields:** - `server_id` (string, required): Unique identifier for your server instance - `channel_id` (string, required): The Discord channel ID (or equivalent) this mod serves #### Bot Handshake For bots (Discord bots, etc.): ```json { "type": "bot", "data": { "bot_id": "discord-bot-123" } } ``` **Fields:** - `bot_id` (string, required): The bots ID **Note:** Only **one bot connection** is allowed at a time. Trying to connect a new bot will result in an `409 Conflict` Error. ### Step 3: Receive Acknowledgment After sending the handshake, wait for an acknowledgment: **Success Response:** ```json { "status": "connected", "type": "mod|bot" } ``` **Error Responses:** ```json { "message": "Malformed handshake.", "code": 400 } ``` ```json { "message": "Bot already connected.", "code": 409 } ``` ### Step 4: Begin Message Exchange Once acknowledged, the connection is established and you can start sending/receiving messages. --- ## Message Protocol ### Sending Messages After handshake, send messages as JSON: #### From Mod to Bot ```json { "msg_id": "msg-unique-123", "id": "minecraft-server-001", "destination": { "channel_id": "123456789" }, "author": { "id": "player-uuid-abc", "name": "PlayerName" }, "content": "Hello from the game server!", "meta": { "server_name": "Survival Server", "world": "overworld" }, "ts": "2025-12-08T10:30:00Z" } ``` **Required Fields:** - `msg_id` (string): Unique message identifier (generate client-side) - `id` (string): Server ID (must match your handshake `server_id`) - `destination.channel_id` (string): Target channel ID - `author.id` (string): User/player identifier - `content` (string): Message content (non-empty) **Optional Fields:** - `author.name` (string): Display name for the author - `meta` (object): Additional metadata (arbitrary key-value pairs) - `ts` (RFC3339 timestamp): Message timestamp (defaults to server time if omitted) #### From Bot to Mod ```json { "msg_id": "discord-msg-456", "id": "123456789", "author": { "id": "discord-user-789", "name": "DiscordUser" }, "content": "Hello from Discord!", "meta": { "platform": "discord", "roles": ["admin"] }, "ts": "2025-12-08T10:31:00Z" } ``` **Required Fields:** - `msg_id` (string): Unique message identifier - `id` (string): Channel ID (from which channel the message originates) - `author.id` (string): User identifier - `content` (string): Message content (non-empty) **Optional Fields:** - `author.name` (string): Display name - `meta` (object): Additional metadata - `ts` (RFC3339 timestamp): Message timestamp **Note:** Bot messages do **not** include a `destination` field, as the channel ID in `id` determines routing. ### Receiving Messages Messages are received as JSON in the same format they were sent: #### Mod Receives (from Bot) ```json { "type": "bot", "channel_id": "123456789", "author": { "id": "discord-user-789", "name": "DiscordUser" }, "content": "Hello from Discord!", "meta": { "platform": "discord" }, "ts": "2025-12-08T10:31:00Z", "received_at": "2025-12-08T10:31:00.123Z", "forwarded_at": "2025-12-08T10:31:00.125Z" } ``` #### Bot Receives (from Mod) ```json { "type": "mod", "channel_id": "123456789", "author": { "id": "player-uuid-abc", "name": "PlayerName" }, "content": "Hello from the game server!", "meta": { "server_name": "Survival Server" }, "ts": "2025-12-08T10:30:00Z", "received_at": "2025-12-08T10:30:00.100Z", "forwarded_at": "2025-12-08T10:30:00.102Z" } ``` **Additional Fields in Received Messages:** - `type` (string): Origin type ("mod" or "bot") - `channel_id` (string): The channel this message belongs to - `received_at` (RFC3339): When gateway received the message - `forwarded_at` (RFC3339): When gateway forwarded the message ### Message Acknowledgments After sending each message, you'll receive an acknowledgment: ```json { "status": "completed", "type": "mod" } ``` **Status Values:** - `completed`: Message was delivered immediately to recipient - `queued`: Recipient is offline; message queued for later delivery - `failed`: Message could not be delivered or queued --- ## Message Queuing When a recipient is offline, messages are automatically queued: - **Queue Size:** Configurable (default: 8 messages per channel) - **Queue Behavior:** Circular buffer (oldest messages are overwritten when full) - **Flush Trigger:** When recipient reconnects, all queued messages are delivered ### Example Flow 1. Mod sends message while bot is offline → Message queued 2. Bot connects and completes handshake → All queued messages flushed to bot 3. Bot sends message while mod is offline → Message queued 4. Mod connects → Queued messages flushed to mod --- ## Keep-Alive & Ping/Pong The gateway sends **WebSocket ping messages every 30 seconds** to maintain connections. **Client Responsibilities:** 1. **Respond to pings**: Your WebSocket library should automatically handle pong responses 2. **Handle pongs**: Set a pong handler to reset read deadlines 3. **Read Deadline**: The gateway sets a 60-second read deadline, reset on each pong ### Example (Go) ```go conn.SetPongHandler(func(appData string) error { conn.SetReadDeadline(time.Now().Add(60 * time.Second)) return nil }) ``` --- ## Error Handling ### Connection Errors **Handshake Errors:** - `400` - Malformed handshake JSON - `401` - Invalid or missing API key - `409` - Bot already connected (only for bot handshakes) - `500` - Internal server error **Message Errors:** - `400` - Malformed message (missing required fields) Errors are sent as JSON: ```json { "message": "Malformed message.", "code": 400 } ``` ### Validation Rules Messages are validated on receipt: - `id` must not be empty - `msg_id` must not be empty - `author.id` must not be empty - `content` must not be empty - For mod messages: `destination.channel_id` must not be empty ### Websocket Closures - `1000` - Normal closure - `1001` - Going away Handle other WebSocket closures as unexpected errors. --- ## Rate Limits & Restrictions - **Message Size Limit:** 2 MB per message (configurable) - **Read Limit:** Messages exceeding the limit will close the connection - **Concurrent Mods:** Multiple mods *can* connect to the same channel ID (different server IDs) - **Concurrent Bots:** Only **one bot connection** allowed globally --- ## Best Practices ### 1. Generate Unique Message IDs Always generate unique `msg_id` values for each message. Consider using: - UUID v4 - Timestamp + random suffix - Sequential counter with prefix ### 2. Handle Reconnections Implement automatic reconnection logic with exponential backoff: ``` 1st retry: 1 second 2nd retry: 2 seconds 3rd retry: 4 seconds Max: 30 seconds ``` ### 3. Set Appropriate Timeouts - Write timeout: 5 seconds (same as Gateway) - Read timeout: 60 seconds (reset on pong) ### 4. Validate Before Sending Check required fields locally before sending to avoid validation errors. ### 5. Monitor Acknowledgments Track acknowledgment statuses: - `completed`: Message delivered - `queued`: Message queued - `failed`: Log and potentially retry ### 6. Use Metadata The `meta` field is used for: - Server information (server name, region, version) - User context (roles, permissions) - Message context (reply-to, thread-id) ### 7. Thread-Safe Writes Use mutex/locks when writing to WebSocket from multiple threads. --- ## API Reference ### Endpoints #### `GET /sync` WebSocket upgrade endpoint for mod/bot connections. **Query Parameters:** - `api_key` (required): Authentication token #### `GET /health` Health check endpoint. **Response:** ```json { "status": "healthy" } ``` --- ## Configuration Gateway configuration (`config.toml`): ```toml [gateway] http_port = 3333 # WebSocket port websocket = "gateway" # API key body_size = 1 # Max message size in MB queue_max = 8 # Messages per channel queue ``` ---