disruption_gateway 0.2.0

A small wrapper around the Discord gateway.
Documentation

disruption_gateway

License: MIT

Low-level WebSocket gateway connection manager for the Discord API. This crate handles the WebSocket connection lifecycle, heartbeats, reconnection logic, and message routing.

Part of the Disruption Discord API wrapper ecosystem.

Overview

disruption_gateway provides production-ready gateway connection management with automatic reconnection, RESUME support, and reliable event delivery. It handles all the low-level details of maintaining a persistent WebSocket connection to Discord's gateway.

Features

  • Automatic Connection Management: Connects to Discord's gateway and maintains the connection
  • Heartbeat System: Automatic heartbeat sending to keep the connection alive
  • RESUME Support: Resumes sessions after disconnections to prevent event loss
  • Sequence Tracking: Tracks message sequence numbers for reliable event delivery
  • Exponential Backoff: Smart reconnection strategy to handle outages gracefully
  • Async/Await: Built on Tokio and tokio-tungstenite for high-performance async I/O
  • Session Management: Maintains session state across reconnections

When to Use

Use disruption_gateway directly if:

  • You need low-level control over the gateway connection
  • You're building a custom Discord client implementation
  • You want to handle raw gateway payloads yourself

Use the main disruption crate if:

  • You're building a Discord bot (recommended for most users)
  • You want high-level event handlers and helpers
  • You prefer working with typed events rather than raw payloads

Installation

Add this to your Cargo.toml:

[dependencies]
disruption_gateway = "0.1.0"
tokio = { version = "1.47", features = ["full"] }

Quick Start

use disruption_gateway::Gateway;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to Discord's gateway
    let gateway = Gateway::connect("YOUR_BOT_TOKEN").await?;

    // Get the receiver for gateway events
    let receiver = gateway.receiver().await.clone();

    // Process incoming payloads
    loop {
        let payload = receiver.recv().await?;
        println!("Received event: {:?}", payload.t);
    }
}

Architecture

Connection Lifecycle

  1. Connect: Establishes WebSocket connection to Discord's gateway
  2. Hello: Receives HELLO opcode with heartbeat interval
  3. Identify/Resume: Sends IDENTIFY (first connection) or RESUME (reconnection)
  4. Ready: Receives READY event with session information
  5. Events: Receives and processes gateway events
  6. Heartbeat: Sends periodic heartbeats to maintain connection
  7. Reconnect: Handles disconnections and attempts RESUME

Gateway Struct

The Gateway struct manages the entire connection lifecycle:

pub struct Gateway {
    token: String,
    writer: WriterLock,
    rec_tuple: (Sender<Payload>, Receiver<Payload>),
    receiver_handle: Option<JoinHandle<()>>,
    heartbeat_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
}

Key Methods:

  • connect(token): Establishes a connection and returns a Gateway instance
  • receiver(): Returns the receiver for incoming payloads
  • seq_num(): Returns the current sequence number
  • session_id(): Returns the current session ID (if available)
  • set_session_id(): Sets the session ID (used internally)

Resilience Features

1. RESUME Support

When the connection drops, the gateway attempts to RESUME the session:

// Gateway automatically tracks:
// - Last sequence number received
// - Session ID from READY event

// On reconnection:
if let (Some(session_id), Some(seq)) = (gateway.session_id(), gateway.seq_num()) {
    // Send RESUME payload
    // Discord replays any missed events
} else {
    // Send IDENTIFY payload (new session)
}

Benefits:

  • Zero event loss during network interruptions
  • Faster reconnection (no need to re-identify)
  • Seamless recovery from transient failures

2. Sequence Number Tracking

Every Dispatch event (opcode 0) includes a sequence number:

// Gateway tracks the sequence number from each event
let payload = receiver.recv().await?;
if payload.op == GatewayOpcode::Dispatch {
    // Sequence number automatically tracked internally
}

Benefits:

  • Enables RESUME functionality
  • Ensures event ordering
  • Detects missed events

3. Exponential Backoff

Reconnection delays increase exponentially:

Attempt 1: 1 second
Attempt 2: 2 seconds
Attempt 3: 4 seconds
Attempt 4: 8 seconds
Attempt 5: 16 seconds
Attempt 6: 32 seconds
Attempt 7+: 60 seconds (max)

Benefits:

  • Reduces server load during outages
  • Prevents rate limiting
  • Follows Discord's recommended strategy
  • Resets to 1s after successful connection

4. Automatic Heartbeats

Heartbeats are sent automatically based on the interval provided in HELLO:

// Gateway spawns a heartbeat task that:
// 1. Waits for the specified interval
// 2. Sends a Heartbeat payload with current sequence number
// 3. Expects a HeartbeatACK response
// 4. Reconnects if ACK not received

Benefits:

  • Keeps connection alive
  • Detects zombie connections
  • Automatic recovery on timeout

Payload Structure

Gateway events are delivered as Payload structs:

pub struct Payload {
    pub op: GatewayOpcode,      // Operation code (0-11)
    pub d: Option<Value>,        // Event data (JSON)
    pub s: Option<u64>,          // Sequence number
    pub t: Option<String>,       // Event name
}

Gateway Opcodes

pub enum GatewayOpcode {
    Dispatch = 0,           // Event dispatched
    Heartbeat = 1,          // Heartbeat sent by client
    Identify = 2,           // Session start
    PresenceUpdate = 3,     // Presence update
    VoiceStateUpdate = 4,   // Voice state update
    Resume = 6,             // Resume disconnected session
    Reconnect = 7,          // Server requests reconnect
    RequestGuildMembers = 8,// Request guild member info
    InvalidSession = 9,     // Session invalidated
    Hello = 10,             // Connection established
    HeartbeatACK = 11,      // Heartbeat acknowledged
}

Advanced Usage

Accessing Session State

let gateway = Gateway::connect(token).await?;

// Get current sequence number
if let Some(seq) = gateway.seq_num().await {
    println!("Current sequence: {}", seq);
}

// Get session ID
if let Some(session_id) = gateway.session_id().await {
    println!("Session ID: {}", session_id);
}

Processing Different Event Types

use disruption_gateway::Gateway;
use disruption_types::opcodes::GatewayOpcode;

let gateway = Gateway::connect(token).await?;
let receiver = gateway.receiver().await.clone();

loop {
    let payload = receiver.recv().await?;

    match payload.op {
        GatewayOpcode::Dispatch => {
            // Gateway event (MESSAGE_CREATE, GUILD_CREATE, etc.)
            if let Some(event_name) = payload.t {
                println!("Event: {}", event_name);
                // payload.d contains the event data
            }
        }
        GatewayOpcode::HeartbeatACK => {
            println!("Heartbeat acknowledged");
        }
        GatewayOpcode::Reconnect => {
            println!("Server requested reconnect");
        }
        GatewayOpcode::InvalidSession => {
            println!("Session invalidated");
        }
        _ => {}
    }
}

Dependencies

  • tokio: Async runtime
  • tokio-tungstenite: WebSocket client
  • futures-util: Stream utilities
  • async-channel: Multi-producer, multi-consumer channels
  • serde_json: JSON serialization
  • log: Logging facade
  • url: URL parsing
  • disruption_types: Shared type definitions

Error Handling

The gateway handles most errors internally and attempts to recover:

  • Connection errors: Automatic reconnection with exponential backoff
  • Invalid session: Attempts RESUME, falls back to IDENTIFY
  • Heartbeat timeout: Reconnects and resumes session
  • WebSocket errors: Logs error and reconnects

Critical errors are propagated to the caller through the Result type.

Logging

Enable logging to see gateway activity:

env_logger::init();

// Logs include:
// - Connection attempts and failures
// - IDENTIFY/RESUME messages
// - Heartbeat activity
// - Sequence number updates
// - Reconnection backoff delays

Set log level with the RUST_LOG environment variable:

RUST_LOG=disruption_gateway=debug cargo run

Thread Safety

The Gateway struct uses Arc<Mutex<>> internally for thread-safe access to:

  • WebSocket writer
  • Heartbeat task handle
  • Sequence number
  • Session ID

This allows the gateway to be used across multiple async tasks safely.

Performance

  • Zero-Copy: Events are passed via channels with minimal copying
  • Async I/O: Non-blocking WebSocket operations
  • Efficient Serialization: Uses serde_json for fast JSON parsing
  • Minimal Allocations: Reuses buffers where possible

Comparison with Other Crates

Feature disruption_gateway serenity twilight
RESUME Support
Exponential Backoff
Standalone Gateway
Lightweight
Raw Payload Access

License

This project is licensed under the MIT License - see the LICENSE file for details.

Links


Part of the Disruption ecosystem | Built with ❤️ in Rust