> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Sessions

> Managing WebSocket connections and session lifecycle

A Session represents a single WebSocket connection. Each connected client has its own session with a unique ID and associated value from authentication.

## Session Lifecycle

```
Client connects (HTTP upgrade request)
      │
      ▼
┌─────────────┐
│   OnAuth    │ ──── Error ────▶ Connection rejected (401)
└─────────────┘
      │
   Returns value
      │
      ▼
┌─────────────┐
│  Session    │ ◀──── Session created with ID and value
│  Created    │
└─────────────┘
      │
      ▼
┌─────────────┐
│   Active    │ ◀──── Send/Receive messages
└─────────────┘       OnMessage called for each message
      │
   Client disconnects
   or server closes
   or queue full
      │
      ▼
┌─────────────┐
│  OnClose    │ ──── Session destroyed
└─────────────┘
```

## Session Methods

### ID

Returns the session's unique identifier.

```go theme={null}
OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    sessionID := s.ID()
    log.Printf("Message from session: %s", sessionID)
}
```

### Value

Returns the value stored during authentication.

```go theme={null}
OnAuth: func(ctx context.Context, r *http.Request) (any, error) {
    // Store user info
    return UserInfo{ID: "123", Name: "Alice"}, nil
}

OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    // Retrieve user info
    user := s.Value().(UserInfo)
    log.Printf("Message from user: %s", user.Name)
}
```

### Send

Queues a message for delivery to the client. Non-blocking.

```go theme={null}
// Send a message
err := s.Send(live.Message{
    Topic: "notification",
    Data:  []byte(`{"title":"Hello"}`),
})

if err == live.ErrSessionClosed {
    log.Println("Session already closed")
} else if err == live.ErrQueueFull {
    log.Println("Queue full, session closed")
}
```

### Close

Closes the connection gracefully.

```go theme={null}
// Kick a user
s.Close()
```

### IsClosed

Checks if the session is closed.

```go theme={null}
if s.IsClosed() {
    return // Don't send to closed session
}
```

### CloseError

Returns the error that caused the session to close.

```go theme={null}
OnClose: func(s *live.Session, err error) {
    closeErr := s.CloseError()
    if closeErr == live.ErrQueueFull {
        log.Println("Client couldn't keep up")
    }
}
```

## Subscription Management

Subscriptions are managed through the server, not the session:

```go theme={null}
// Subscribe a session to a topic
server.Subscribe(s, "chat:room1")

// Unsubscribe from a topic
server.Unsubscribe(s, "chat:room1")
```

When a session closes, it's automatically unsubscribed from all topics.

## Common Patterns

### User Authentication

```go theme={null}
type UserInfo struct {
    ID   string
    Name string
    Role string
}

server := live.New(live.Options{
    OnAuth: func(ctx context.Context, r *http.Request) (any, error) {
        token := r.Header.Get("Authorization")
        user, err := validateToken(token)
        if err != nil {
            return nil, err
        }
        return user, nil
    },

    OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
        user := s.Value().(UserInfo)

        // Check permissions
        if topic == "admin" && user.Role != "admin" {
            s.Send(live.Message{Topic: "error", Data: []byte(`"unauthorized"`)})
            return
        }
        // Handle message...
    },
})
```

### Auto-Subscribe on Connect

Since there's no `OnConnect` hook, use `OnMessage` with a special "init" topic:

```go theme={null}
OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    user := s.Value().(UserInfo)

    switch topic {
    case "init":
        // Subscribe to default topics
        server.Subscribe(s, "user:"+user.ID)
        server.Subscribe(s, "global")

        // Send welcome message
        s.Send(live.Message{
            Topic: "welcome",
            Data:  []byte(`{"status":"connected"}`),
        })

    case "subscribe":
        // Handle explicit subscription requests
        var req struct{ Topic string `json:"topic"` }
        json.Unmarshal(data, &req)
        server.Subscribe(s, req.Topic)
    }
}
```

Client sends init on connect:

```javascript theme={null}
ws.onopen = () => {
    ws.send(JSON.stringify({topic: 'init', data: {}}));
};
```

### Presence Tracking

```go theme={null}
var onlineUsers sync.Map

server := live.New(live.Options{
    OnAuth: func(ctx context.Context, r *http.Request) (any, error) {
        userID := r.URL.Query().Get("user")
        return userID, nil
    },

    OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
        userID := s.Value().(string)

        if topic == "init" {
            // Mark user online
            onlineUsers.Store(userID, s.ID())

            // Subscribe to presence
            server.Subscribe(s, "presence")

            // Notify others
            server.Publish("presence", []byte(`{"type":"join","user":"`+userID+`"}`))
        }
    },

    OnClose: func(s *live.Session, err error) {
        userID := s.Value().(string)

        // Mark offline
        onlineUsers.Delete(userID)

        // Notify others
        server.Publish("presence", []byte(`{"type":"leave","user":"`+userID+`"}`))
    },
})
```

### Direct Messaging

```go theme={null}
// Store sessions by user ID
var userSessions sync.Map  // userID -> *live.Session

OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    userID := s.Value().(string)

    switch topic {
    case "init":
        userSessions.Store(userID, s)

    case "dm":
        var req struct {
            To   string `json:"to"`
            Text string `json:"text"`
        }
        json.Unmarshal(data, &req)

        // Find recipient
        if recipient, ok := userSessions.Load(req.To); ok {
            sess := recipient.(*live.Session)
            msg, _ := json.Marshal(map[string]string{
                "from": userID,
                "text": req.Text,
            })
            sess.Send(live.Message{Topic: "dm", Data: msg})
        }
    }
},

OnClose: func(s *live.Session, err error) {
    userID := s.Value().(string)
    userSessions.Delete(userID)
},
```

### Room Management

```go theme={null}
OnMessage: func(ctx context.Context, s *live.Session, topic string, data []byte) {
    userID := s.Value().(string)

    switch topic {
    case "join_room":
        var req struct{ Room string `json:"room"` }
        json.Unmarshal(data, &req)

        // Subscribe to room
        server.Subscribe(s, "room:"+req.Room)

        // Notify room
        msg, _ := json.Marshal(map[string]string{"type": "join", "user": userID})
        server.Publish("room:"+req.Room, msg)

    case "leave_room":
        var req struct{ Room string `json:"room"` }
        json.Unmarshal(data, &req)

        // Notify room
        msg, _ := json.Marshal(map[string]string{"type": "leave", "user": userID})
        server.Publish("room:"+req.Room, msg)

        // Unsubscribe
        server.Unsubscribe(s, "room:"+req.Room)

    case "room_message":
        var req struct {
            Room string `json:"room"`
            Text string `json:"text"`
        }
        json.Unmarshal(data, &req)

        msg, _ := json.Marshal(map[string]string{
            "type": "message",
            "user": userID,
            "text": req.Text,
        })
        server.Publish("room:"+req.Room, msg)
    }
}
```

## Errors

| Error              | Description                            |
| ------------------ | -------------------------------------- |
| `ErrSessionClosed` | Session is already closed              |
| `ErrQueueFull`     | Send queue is full, session was closed |

## Best Practices

### 1. Store Minimal Data in Value

```go theme={null}
// Good: just the user ID
return userID, nil

// Okay: small struct
return UserInfo{ID: "123", Role: "admin"}, nil

// Avoid: large objects
return entireUserObject, nil  // Bad
```

### 2. Handle Close Gracefully

```go theme={null}
OnClose: func(s *live.Session, err error) {
    userID := s.Value().(string)

    // Clean up all resources
    onlineUsers.Delete(userID)
    userSessions.Delete(userID)

    // Notify others if needed
    server.Publish("presence", []byte(`{"type":"leave","user":"`+userID+`"}`))
}
```

### 3. Check Before Sending

```go theme={null}
if !s.IsClosed() {
    s.Send(message)
}
```

### 4. Handle Reconnection

Clients will reconnect. Design your protocol to handle it:

```javascript theme={null}
// Client-side reconnection
function connect() {
    ws = new WebSocket(url);
    ws.onopen = () => {
        // Re-initialize on every connect
        ws.send(JSON.stringify({topic: 'init', data: {}}));
    };
    ws.onclose = () => setTimeout(connect, 1000);
}
```
