478 lines
11 KiB
Markdown
478 lines
11 KiB
Markdown
# 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://<host>:<port>/sync?api_key=<your_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://<host>:<port>/sync?api_key=<your_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
|
||
```
|
||
|
||
---
|