# Server Hooks

The `ServerHooks` trait provides a plugin system for extending the quicproquo
server. Hooks fire at key points in the request lifecycle — message delivery,
authentication, channel creation, and message fetch — allowing you to inspect,
log, rate-limit, or reject operations without modifying server internals.

---

## Overview

```text
Client RPC request
  └─ Validation (auth, rate limits, wire format)
       └─ Hook fires (on_message_enqueue, on_auth, etc.)
            ├─ HookAction::Continue → proceed to storage/delivery
            └─ HookAction::Reject("reason") → error returned to client
```

Hooks are called **synchronously** in the RPC handler path after validation
but before storage. Keep hook implementations fast — offload heavy work
(HTTP calls, disk I/O, analytics) to background tasks.

---

## The `ServerHooks` trait

```rust,ignore
pub trait ServerHooks: Send + Sync {
    /// Called before a message is stored in the delivery queue.
    /// Return HookAction::Reject to prevent delivery.
    fn on_message_enqueue(&self, event: &MessageEvent) -> HookAction {
        HookAction::Continue
    }

    /// Called after a batch of messages is enqueued.
    fn on_batch_enqueue(&self, events: &[MessageEvent]) {}

    /// Called after a successful or failed login attempt.
    fn on_auth(&self, event: &AuthEvent) {}

    /// Called after a channel is created or looked up.
    fn on_channel_created(&self, event: &ChannelEvent) {}

    /// Called after messages are fetched from the delivery queue.
    fn on_fetch(&self, event: &FetchEvent) {}

    /// Called when a user completes OPAQUE registration.
    fn on_user_registered(&self, username: &str, identity_key: &[u8]) {}
}
```

All methods have default no-op implementations. Override only the events you
care about.

---

## Hook action

```rust,ignore
pub enum HookAction {
    /// Allow the operation to proceed.
    Continue,
    /// Reject the operation with a reason (returned to the client as an error).
    Reject(String),
}
```

Currently only `on_message_enqueue` can reject operations. Other hooks are
observational (fire-and-forget).

---

## Event types

### `MessageEvent`

Fired on `enqueue` and `batch_enqueue` RPC calls.

| Field              | Type              | Description |
|--------------------|-------------------|-------------|
| `sender_identity`  | `Option<Vec<u8>>` | Sender's 32-byte identity key (None in sealed sender mode). |
| `recipient_key`    | `Vec<u8>`         | Recipient's 32-byte identity key. |
| `channel_id`       | `Vec<u8>`         | 16-byte channel ID. |
| `payload_len`      | `usize`           | Length of the encrypted payload in bytes. |
| `seq`              | `u64`             | Server-assigned sequence number. |

### `AuthEvent`

Fired after OPAQUE login completes (success or failure).

| Field            | Type     | Description |
|------------------|----------|-------------|
| `username`       | `String` | The username that attempted to authenticate. |
| `success`        | `bool`   | Whether authentication succeeded. |
| `failure_reason` | `String` | Failure reason (empty on success). |

### `ChannelEvent`

Fired after a `createChannel` RPC call.

| Field           | Type       | Description |
|-----------------|------------|-------------|
| `channel_id`    | `Vec<u8>`  | 16-byte channel ID. |
| `initiator_key` | `Vec<u8>`  | Identity key of the channel initiator. |
| `peer_key`      | `Vec<u8>`  | Identity key of the peer. |
| `was_new`       | `bool`     | True if this is a newly created channel. |

### `FetchEvent`

Fired after a `fetch` or `fetchWait` RPC call.

| Field           | Type       | Description |
|-----------------|------------|-------------|
| `recipient_key` | `Vec<u8>`  | Identity key of the fetcher. |
| `channel_id`    | `Vec<u8>`  | Channel ID being fetched from. |
| `message_count` | `usize`    | Number of messages returned. |

---

## Built-in implementations

### `NoopHooks`

Does nothing. This is the default when no hooks are configured.

```rust,ignore
pub struct NoopHooks;
impl ServerHooks for NoopHooks {}
```

### `TracingHooks`

Logs all events via the `tracing` crate at info/debug level.

```rust,ignore
pub struct TracingHooks;

impl ServerHooks for TracingHooks {
    fn on_message_enqueue(&self, event: &MessageEvent) -> HookAction {
        tracing::info!(
            recipient_prefix = %hex_prefix(&event.recipient_key),
            payload_len = event.payload_len,
            seq = event.seq,
            "hook: message enqueued"
        );
        HookAction::Continue
    }

    fn on_auth(&self, event: &AuthEvent) {
        if event.success {
            tracing::info!(username = %event.username, "hook: login success");
        } else {
            tracing::warn!(
                username = %event.username,
                reason = %event.failure_reason,
                "hook: login failure"
            );
        }
    }
    // ... other methods log similarly
}
```

---

## Writing a custom hook

### Example: payload size limiter

```rust,ignore
use quicproquo_server::hooks::{ServerHooks, HookAction, MessageEvent};

struct PayloadLimiter {
    max_bytes: usize,
}

impl ServerHooks for PayloadLimiter {
    fn on_message_enqueue(&self, event: &MessageEvent) -> HookAction {
        if event.payload_len > self.max_bytes {
            return HookAction::Reject(format!(
                "payload too large: {} > {} bytes",
                event.payload_len, self.max_bytes
            ));
        }
        HookAction::Continue
    }
}
```

### Example: login auditor

```rust,ignore
use quicproquo_server::hooks::{ServerHooks, AuthEvent};

struct LoginAuditor;

impl ServerHooks for LoginAuditor {
    fn on_auth(&self, event: &AuthEvent) {
        if !event.success {
            eprintln!(
                "AUDIT: failed login for '{}': {}",
                event.username, event.failure_reason
            );
        }
    }
}
```

### Example: composing multiple hooks

```rust,ignore
use quicproquo_server::hooks::*;

struct CompositeHooks {
    hooks: Vec<Box<dyn ServerHooks>>,
}

impl ServerHooks for CompositeHooks {
    fn on_message_enqueue(&self, event: &MessageEvent) -> HookAction {
        for hook in &self.hooks {
            if let HookAction::Reject(reason) = hook.on_message_enqueue(event) {
                return HookAction::Reject(reason);
            }
        }
        HookAction::Continue
    }

    fn on_auth(&self, event: &AuthEvent) {
        for hook in &self.hooks {
            hook.on_auth(event);
        }
    }
    // ... delegate other methods similarly
}
```

---

## Important considerations

- **E2E encryption**: Message payloads are encrypted end-to-end. Hooks cannot
  inspect plaintext content — they see only metadata (sender, recipient,
  payload size, sequence number).
- **Performance**: Hooks run synchronously in the RPC handler. A slow hook
  blocks the RPC response. Use `tokio::spawn` for async work.
- **Thread safety**: `ServerHooks` requires `Send + Sync`. Use `Arc<Mutex<_>>`
  or lock-free structures for shared mutable state.
- **Reject semantics**: Only `on_message_enqueue` supports rejection. Other
  hooks are informational — the operation proceeds regardless of what the hook
  does.

---

## Further reading

- [Delivery Service Internals](delivery-service.md) -- how messages flow through the server
- [Authentication Service Internals](authentication-service.md) -- OPAQUE auth flow
- [Bot SDK](../getting-started/bot-sdk.md) -- build bots that interact with the server
