### Adler Quick Start Example Source: https://github.com/catalinfl/adler/blob/master/README.md A basic example demonstrating how to set up an Adler WebSocket server. It includes handlers for connection, message reception, and error handling, and serves the WebSocket endpoint on /ws. ```go package main import ( "log" "net/http" "github.com/catalinfl/adler" ) func main() { a := adler.New() a.HandleConnect(func(s *adler.Session) { log.Println("connected:", s.RemoteAddr()) }) a.HandleMessage(func(s *adler.Session, msg []byte) { _ = s.WriteText([]byte("echo: " + string(msg))) }) a.HandleError(func(s *adler.Session, err error) { log.Println("ws error:", err) }) http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { if err := a.HandleRequest(w, r); err != nil { log.Println("handle request:", err) } }) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` -------------------------------- ### Minimal Room Example Source: https://github.com/catalinfl/adler/blob/master/README.md A basic example demonstrating how to create a room, handle client connections, and allow clients to join the room. ```APIDOC ## Minimal Room Example ```go room := a.NewRoom("test") a.HandleConnect(func(s *adler.Session) { _ = room.Join(s) }) room.HandleJoin(func(s *adler.Session) { _ = s.WriteText([]byte("joined room")) }) ``` ``` -------------------------------- ### Install Adler Go Package Source: https://github.com/catalinfl/adler/blob/master/README.md Use 'go get' to install the Adler package. This command fetches and installs the latest version of the library. ```bash go get github.com/catalinfl/adler ``` -------------------------------- ### Example Session Storage Usage Source: https://github.com/catalinfl/adler/blob/master/README.md A practical example demonstrating the use of session storage and message writing. ```APIDOC Example session storage: ```go a.HandleConnect(func(s *adler.Session) { s.Set("role", "admin") s.SetNX("seen", true) }) a.HandleMessage(func(s *adler.Session, msg []byte) { role, _ := s.GetString("role") _ = s.WriteText([]byte("role=" + role + " msg=" + string(msg))) }) ``` ``` -------------------------------- ### Create Adler Server with Options Source: https://github.com/catalinfl/adler/blob/master/README.md Instantiate a new Adler server with custom configuration options. This example shows how to set asynchronous dispatch, message buffer size, and ping period. ```go a := adler.New( adler.WithDispatchAsync(true), adler.WithMessageBufferSize(256), adler.WithPingPeriod(30*time.Second), ) ``` -------------------------------- ### Adler Session Storage Example Source: https://github.com/catalinfl/adler/blob/master/README.md Demonstrates using the Session's key-value store to set and retrieve data. This example sets a 'role' and checks it when a message is received. ```go a.HandleConnect(func(s *adler.Session) { s.Set("role", "admin") s.SetNX("seen", true) }) a.HandleMessage(func(s *adler.Session, msg []byte) { role, _ := s.GetString("role") _ = s.WriteText([]byte("role=" + role + " msg=" + string(msg))) }) ``` -------------------------------- ### Manage Client Groups in Rooms Source: https://github.com/catalinfl/adler/blob/master/README.md Rooms help manage subsets of sessions. Use `NewRoom` to create or get a room, `Join` to add a session, and `Broadcast` to send messages within the room. Sessions can be automatically joined on connect. ```go room := a.NewRoom("lobby") a.HandleConnect(func(s *adler.Session) { _ = room.Join(s) }) room.Broadcast([]byte("welcome to the lobby")) ``` ```go room := a.NewRoom("test") a.HandleConnect(func(s *adler.Session) { _ = room.Join(s) }) room.HandleJoin(func(s *adler.Session) { _ = s.WriteText([]byte("joined room")) }) ``` -------------------------------- ### Session Key-Value Store Source: https://context7.com/catalinfl/adler/llms.txt Explains how to use the concurrent-safe key-value store within a session for setting, getting, and managing data, including atomic operations and typed accessors. ```APIDOC ## Session Key-Value Store ### Description Every session carries a concurrent-safe `map[string]any` store. Typed accessors avoid manual type assertions. ### Methods - `Set(string, any)`: Sets a key-value pair. - `SetNX(string, any)`: Sets a key-value pair only if the key does not exist, returns bool. - `GetString(string)`: Gets a value as a string. - `GetInt64(string)`: Gets a value as an int64. - `GetBool(string)`: Gets a value as a bool. - `Has(string)`: Checks if a key exists. - `Incr(string)`: Atomically increments an `*int64` value. - `Decr(string)`: Atomically decrements an `*int64` value. - `Keys()`: Returns a snapshot of all key names. - `Values()`: Returns a snapshot of all values. - `Unset(string)`: Removes a key. - `Clear()`: Removes all keys. ``` -------------------------------- ### Register Adler Session Handlers Source: https://github.com/catalinfl/adler/blob/master/README.md Register callback functions for different stages of the WebSocket session lifecycle. This example shows handlers for connection and disconnection events. ```go a.HandleConnect(func(s *adler.Session) { s.Set("userID", "123") }) a.HandleClose(func(s *adler.Session, code int, reason string) { log.Printf("client closed: code=%d reason=%q", code, reason) }) ``` -------------------------------- ### Adler Server Creation and Configuration Source: https://github.com/catalinfl/adler/blob/master/README.md Demonstrates how to create a new Adler server instance and apply configuration options. ```APIDOC ## Create a server Use `adler.New()` to build a server instance. You can pass configuration options at construction time: ```go a := adler.New( adler.WithDispatchAsync(true), adler.WithMessageBufferSize(256), adler.WithPingPeriod(30*time.Second), ) ``` ``` -------------------------------- ### Initialize WebSocket and UI Elements Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Sets up event listeners for UI elements and establishes the WebSocket connection. Handles initial name and room settings. ```javascript const messages = document.getElementById('messages'); const form = document.getElementById('form'); const messageInput = document.getElementById('message'); const nameInput = document.getElementById('name'); const renameBtn = document.getElementById('renameBtn'); const statusDot = document.getElementById('statusDot'); const statusText = document.getElementById('statusText'); const endpointText = document.getElementById('endpoint'); const currentNameLabel = document.getElementById('currentNameLabel'); const roomLabel = document.getElementById('roomLabel'); const roomButtons = Array.from(document.querySelectorAll('.room-btn')); const wsScheme = location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsBase = `${wsScheme}//${location.host}/ws`; endpointText.textContent = wsBase; let socket; let currentName = (localStorage.getItem('adler-name') || '').trim(); let currentRoom = 'lobby'; let renameTimer; nameInput.value = currentName; currentNameLabel.textContent = currentName || 'guest'; ``` -------------------------------- ### Create Adler Server Instance with Options Source: https://context7.com/catalinfl/adler/llms.txt Constructs a new Adler server instance with customizable runtime configurations. Options control write deadlines, idle timeouts, ping periods, message buffer sizes, dispatch behavior, room deletion, and protocol selection. ```go package main import ( "time" "github.com/catalinfl/adler" ) func main() { a := adler.New( adler.WithWriteWait(10), // 10 s per-write deadline adler.WithPongWait(60), // 60 s idle timeout (0 = disabled) adler.WithPingPeriod(54), // send ping every 54 s adler.WithMessageBufferSize(128), // outbound queue depth per session adler.WithDispatchAsync(true), // handle each inbound message in its own goroutine adler.WithDeleteRoomOnEmpty(true), // auto-remove rooms when last member leaves adler.WithProtocol(adler.JSON), // JSON (default) or adler.Protobuf ) _ = a // pass to HandleRequest / broadcast helpers } ``` -------------------------------- ### Create and Manage Rooms in Adler Source: https://context7.com/catalinfl/adler/llms.txt Demonstrates creating named rooms, handling join/leave callbacks at both room and server levels, and managing room state. Use this for organizing sessions into distinct channels. ```go a := adler.New(adler.WithDeleteRoomOnEmpty(false)) // Pre-create named rooms lobby := a.NewRoom("lobby") arena := a.NewRoom("arena") // Room-level join/leave callbacks lobby.HandleJoin(func(s *adler.Session) { _ = s.WriteJSON(adler.Map{"type": "room_joined", "room": "lobby"}) }) lobby.HandleLeave(func(s *adler.Session) { log.Println("left lobby:", s.RemoteAddr()) }) // Server-level room event callbacks a.OnRoomJoin(func(s *adler.Session, r *adler.Room) { log.Printf("%s joined room %q (size %d)", s.RemoteAddr(), r.Name(), r.Len()) }) a.OnRoomLeave(func(s *adler.Session, r *adler.Room) { log.Printf("%s left room %q", s.RemoteAddr(), r.Name()) }) a.HandleConnect(func(s *adler.Session) { if err := lobby.Join(s); err != nil { // Join moves session from any previous room log.Println("join error:", err) return } lobby.BroadcastJSON(adler.Map{"type": "system", "msg": "new player joined"}) }) a.HandleMessage(func(s *adler.Session, msg []byte) { // Broadcast to everyone in the sender's current room if r := s.Room(); r != nil { r.Broadcast(msg) } // Move session to arena if string(msg) == "go_arena" { _ = arena.Join(s) // automatically leaves lobby } }) // Room inspection log.Println("lobby size:", lobby.Len()) for _, member := range lobby.Sessions() { // snapshot log.Println(" -", member.RemoteAddr()) } // Control room open/closed state arena.CloseRoom() // blocks new Join calls; returns ErrRoomClosed arena.OpenRoom() // re-opens // Retrieve a room by name if r, err := a.Room("lobby"); err == nil { r.BroadcastBinary([]byte{0x00}) } // Manual delete (room must be empty) _ = a.DeleteRoom("lobby") // returns ErrRoomNotEmpty if sessions remain ``` -------------------------------- ### Implement Matchmaker for Queue-Based Room Creation Source: https://context7.com/catalinfl/adler/llms.txt Sets up a matchmaker to automatically group sessions into rooms based on configurable queue parameters. Handles session queuing, room formation, and communication of match status. ```go package main import ( "log" "net/http" "time" "github.com/catalinfl/adler" matchmaking "github.com/catalinfl/adler/matchmaker" ) func main() { a := adler.New(adler.WithDispatchAsync(true)) mm := matchmaking.NewMatchmaker(a, matchmaking.WithRoomSize(4), // form rooms of 4 matchmaking.WithMinRoomSize(2), // allow partial room with ≥2 players … matchmaking.WithPartialRoomTimeout(30*time.Second), // … if oldest has waited 30 s matchmaking.WithQueueLength(20), // main queue cap; overflow → waiting queue matchmaking.WithCommandBuffer(512), // internal command channel buffer ) a.HandleConnect(func(s *adler.Session) { if err := mm.AddToQueue(s); err != nil { // ErrMatchmakerBusy: command buffer full; retry logic can go here log.Println("queue busy:", err) _ = s.WriteJSON(adler.Map{"error": "queue busy, try again"}) } }) a.HandleMessage(func(s *adler.Session, msg []byte) { if string(msg) == "leave_queue" { mm.RemoveFromQueue(s) // fire-and-forget; worker processes it async } }) a.HandleDisconnect(func(s *adler.Session) { mm.RemoveFromQueue(s) // clean up on disconnect }) a.OnRoomJoin(func(s *adler.Session, r *adler.Room) { log.Printf("match started: session %s joined room %s (size %d)", s.RemoteAddr(), r.Name(), r.Len()) }) http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { if err := a.HandleRequest(w, r); err != nil { log.Println(err) } }) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` -------------------------------- ### Initial Connection and Focus Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Connects to the WebSocket server and sets focus to the message input field when the application loads. ```javascript connect(); messageInput.focus(); ``` -------------------------------- ### adler.New — Create a server instance Source: https://context7.com/catalinfl/adler/llms.txt Constructs a new Adler server instance with optional configuration settings. Options allow customization of write deadlines, ping/pong timeouts, message buffer sizes, dispatch behavior, room cleanup, and protocol selection. ```APIDOC ## adler.New ### Description Constructs a ready-to-use `Adler` instance. Zero or more `Option` values customise the runtime configuration applied at construction time. ### Options - `adler.WithWriteWait(duration)`: Sets the per-write deadline. - `adler.WithPongWait(duration)`: Sets the idle timeout for pong frames. - `adler.WithPingPeriod(duration)`: Sets the interval for sending ping frames. - `adler.WithMessageBufferSize(size)`: Configures the outbound queue depth per session. - `adler.WithDispatchAsync(bool)`: Enables asynchronous handling of inbound messages. - `adler.WithDeleteRoomOnEmpty(bool)`: Enables automatic removal of rooms when they become empty. - `adler.WithProtocol(Protocol)`: Sets the communication protocol (e.g., `adler.JSON`, `adler.Protobuf`). ``` -------------------------------- ### Initialize WebSocket Connection and Event Listeners Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Establishes a WebSocket connection and sets up listeners for connection status, errors, and incoming messages. Handles different message types like 'room_changed' and 'chat'. ```javascript const socket = new WebSocket('ws://localhost:8080'); socket.addEventListener('open', () => setStatus(true, 'Connected')); socket.addEventListener('close', () => setStatus(false, 'Disconnected')); socket.addEventListener('error', () => setStatus(false, 'Connection error')); socket.addEventListener('message', (event) => { try { const payload = JSON.parse(event.data); if (payload.type === 'room_changed' && payload.room) { setCurrentRoom(payload.room); return; } pushMessage(payload, payload.type === 'chat' && payload.from === currentName); } catch { pushMessage({ type: 'system', message: event.data }); } }); ``` -------------------------------- ### Configuration Options Source: https://github.com/catalinfl/adler/blob/master/README.md Lists available configuration options for initializing the Adler server, such as write wait, pong wait, ping period, message buffer size, and asynchronous dispatch. ```APIDOC ## Configuration Use these options with `adler.New(...)`: - `WithWriteWait(time.Duration)` interprets the argument as seconds; `WithWriteWait(10)` means 10 seconds - `WithPongWait(time.Duration)` interprets the argument as seconds; `WithPongWait(60)` means 60 seconds, and `WithPongWait(0)` disables idle disconnects - `WithPingPeriod(time.Duration)` interprets the argument as seconds; `WithPingPeriod(54)` means 54 seconds - `WithMessageBufferSize(int)` sets the outbound queue size; start with 64-256 and increase only if you hit `ErrBufferFull` under normal bursts - `WithDispatchAsync(bool)` switches inbound dispatch to goroutine-per-message when enabled - `WithDeleteRoomOnEmpty(bool)` controls automatic room deletion when the last session leaves (default: `true`) ``` -------------------------------- ### Implement Queue-Based Matchmaking Source: https://github.com/catalinfl/adler/blob/master/README.md The `matchmaker` module provides queue-based matchmaking. Use `NewMatchmaker` to initialize, `AddToQueue` to join, and `RemoveFromQueue` to leave. It automatically creates rooms when enough players are queued and sends JSON notifications for queue status. ```go import "github.com/catalinfl/adler/matchmaker" mm := matchmaking.NewMatchmaker(a, matchmaking.WithRoomSize(4), matchmaking.WithMaxQueue(20), ) a.HandleConnect(func(s *adler.Session) { _ = mm.AddToQueue(s) }) a.HandleMessage(func(s *adler.Session, msg []byte) { if string(msg) == "leave_queue" { mm.RemoveFromQueue(s) } }) ``` -------------------------------- ### Establish WebSocket Connection Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Creates a new WebSocket connection, appending the user's name as a query parameter if provided. Sets up event listeners for 'open' and 'close' events. ```javascript function connect() { const name = currentName || (nameInput.value || '').trim(); const url = name ? `${wsBase}?name=${encodeURIComponent(name)}` : wsBase; socket = new WebSocket(url); socket.addEventListener('open', () => { setStatus(true, 'Connected'); if (name) { nameInput.value = name; setCurrentName(name); } }); socket.addEventListener('close', () => setSta ``` -------------------------------- ### Matchmaking with the Matchmaker Module Source: https://github.com/catalinfl/adler/blob/master/README.md Provides a module for queue-based matchmaking that automatically groups sessions into rooms based on defined criteria. ```APIDOC ## Matchmaking with the Matchmaker Module The `matchmaker` module provides queue-based matchmaking that groups sessions into rooms automatically. It uses a background goroutine to manage queues and ensure fair player distribution. Features: - **Non-blocking queue operations**: `AddToQueue()` and `RemoveFromQueue()` send commands to a worker goroutine - **Main and waiting queues**: Keeps a main queue and an optional waiting queue when the main queue reaches capacity - **Automatic room creation**: Creates Adler rooms when enough players are queued - **JSON event notifications**: Sends events to sessions as they move through the queue The matchmaker sends these JSON messages: - `"queue_joined"` - Session added to main queue - `"wait_queue_joined"` - Session added to waiting queue (main is full) - `"promoted_to_queue"` - Session promoted from waiting to main queue - `"match_found"` - Match room created with `room_id` and player count ### Example ```go import "github.com/catalinfl/adler/matchmaker" mm := matchmaking.NewMatchmaker(a, matchmaking.WithRoomSize(4), matchmaking.WithMaxQueue(20), ) a.HandleConnect(func(s *adler.Session) { _ = mm.AddToQueue(s) }) a.HandleMessage(func(s *adler.Session, msg []byte) { if string(msg) == "leave_queue" { mm.RemoveFromQueue(s) } }) ``` ``` -------------------------------- ### Registering Session Lifecycle Handlers Source: https://github.com/catalinfl/adler/blob/master/README.md Shows how to register handlers for various session events like connection, message reception, and closure. ```APIDOC ## Register handlers before serving requests Handlers are set on the `Adler` instance and are called during the session lifecycle. - `HandleConnect` runs after a session is registered. - `HandleMessage` receives text frames. - `HandleMessageBinary` receives binary frames. - `HandlePong` receives pong frames. - `HandleClose` receives close code and reason. - `HandleSentMessage` and `HandleSentMessageBinary` run after server writes succeed. - `HandleError` receives runtime errors from the session loop. - `OnRoomJoin` and `OnRoomLeave` receive room membership events. Example: ```go a.HandleConnect(func(s *adler.Session) { s.Set("userID", "123") }) a.HandleClose(func(s *adler.Session, code int, reason string) { log.Printf("client closed: code=%d reason=%q", code, reason) }) ``` ``` -------------------------------- ### Broadcast Messages to Clients Source: https://github.com/catalinfl/adler/blob/master/README.md Use server-level broadcast helpers to send messages to multiple sessions. `Broadcast` sends to all, `BroadcastFilter` sends to matching sessions, and `BroadcastOthers` sends to all except a specific session. ```go a.Broadcast([]byte("server says hello")) ``` ```go a.SendTo([]byte("private message"), session) ``` ```go a.BroadcastFilter([]byte("admins only"), func(s *adler.Session) bool { role, _ := s.GetString("role") return role == "admin" }) ``` -------------------------------- ### Session Storage Operations Source: https://github.com/catalinfl/adler/blob/master/README.md Explains how to use the key-value store within a `Session` object for storing and retrieving data. ```APIDOC ### Session Storage Helpers - `Set(key, value)` - `SetNX(key, value)` - `Get(key)` - `GetString(key)` - `GetInt(key)` - `GetInt64(key)` - `GetFloat(key)` - `GetBool(key)` - `Has(key)` - `Unset(key)` - `Keys()` - `Values()` - `Clear()` - `Incr(key)` and `Decr(key)` for `*int64` counters ``` -------------------------------- ### Session Write Operations Source: https://context7.com/catalinfl/adler/llms.txt Demonstrates how to send different types of messages (text, binary, JSON, serializer-aware) from a session, including options for per-call deadlines and closing the session. ```APIDOC ## Session Write Operations ### Description Exposes typed write helpers for text, binary, JSON, and serializer-aware payloads, each with optional per-call write deadlines. ### Methods - `WriteText([]byte)`: Sends plain text. - `WriteTextWithDeadline([]byte, time.Duration)`: Sends plain text with a deadline. - `WriteBinary([]byte)`: Sends binary data. - `WriteBinaryWithDeadline([]byte, time.Duration)`: Sends binary data with a deadline. - `WriteJSON(any)`: Sends JSON-encoded data. - `WriteJSONWithDeadline(any, time.Duration)`: Sends JSON-encoded data with a deadline. - `Write(any)`: Sends serializer-aware data (JSON or Protobuf). - `Close([]byte)`: Sends a close frame with an optional payload. ``` -------------------------------- ### Adler Serialization: JSON and Protobuf Source: https://context7.com/catalinfl/adler/llms.txt Configure Adler to use JSON for text frames or Protobuf for binary frames. Ensure custom types implement proto.Message for Protobuf serialization. ```go a := adler.New(adler.WithProtocol(adler.JSON)) a.HandleConnect(func(s *adler.Session) { _ = s.Write(adler.Map{"status": "connected"}) // marshalled to JSON text frame }) ``` ```go aProto := adler.New(adler.WithProtocol(adler.Protobuf)) aProto.HandleMessage(func(s *adler.Session, msg []byte) { // Unmarshal manually using proto.Unmarshal, then reply: // _ = s.Write(myProtoMessage) // marshalled as binary protobuf frame }) ``` -------------------------------- ### Serving WebSocket Endpoint Source: https://github.com/catalinfl/adler/blob/master/README.md Illustrates how to integrate Adler into an HTTP server to handle WebSocket upgrade requests. ```APIDOC ## Serve the websocket endpoint Call `HandleRequest` from an HTTP handler. It upgrades the connection and blocks until the session ends. ```go http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { _ = a.HandleRequest(w, r) }) ``` ``` -------------------------------- ### Write Messages with Adler Session Source: https://context7.com/catalinfl/adler/llms.txt Use session write helpers for text, binary, JSON, and serializer-aware payloads. Optional per-call write deadlines can be specified. ```go a.HandleConnect(func(s *adler.Session) { // Plain text _ = s.WriteText([]byte("hello")) // Text with per-call deadline _ = s.WriteTextWithDeadline([]byte("timed"), 5*time.Second) // Binary _ = s.WriteBinary([]byte{0x01, 0x02, 0x03}) // Binary with deadline _ = s.WriteBinaryWithDeadline([]byte{0xFF}, 3*time.Second) // JSON using adler.Map (map[string]any alias) _ = s.WriteJSON(adler.Map{ "type": "welcome", "message": "connection established", "ts": time.Now().Unix(), }) // JSON with deadline (accepts any value) _ = s.WriteJSONWithDeadline(map[string]string{"status": "ok"}, 2*time.Second) // Serializer-aware (uses JSON or Protobuf depending on WithProtocol) _ = s.Write(adler.Map{"event": "ready"}) // Send a close frame with optional payload _ = s.Close([]byte("bye")) }) ``` -------------------------------- ### Register Session Lifecycle Handlers Source: https://context7.com/catalinfl/adler/llms.txt Register callbacks for various session lifecycle events including connection, disconnection, message handling (text and binary), close frames, pong frames, sent messages, and errors. These handlers allow custom logic for each stage of a client's session. ```go a := adler.New() a.HandleConnect(func(s *adler.Session) { token := s.Request().URL.Query().Get("token") s.Set("token", token) log.Println("connected:", s.RemoteAddr(), "token:", token) }) a.HandleMessage(func(s *adler.Session, msg []byte) { log.Printf("text from %s: %s", s.RemoteAddr(), msg) _ = s.WriteText([]byte("echo: " + string(msg))) }) a.HandleMessageBinary(func(s *adler.Session, msg []byte) { log.Printf("binary from %s: %d bytes", s.RemoteAddr(), len(msg)) _ = s.WriteBinary(msg) }) a.HandleClose(func(s *adler.Session, code int, reason string) { log.Printf("close: code=%d reason=%q", code, reason) }) a.HandleDisconnect(func(s *adler.Session) { log.Println("disconnected:", s.RemoteAddr()) }) a.HandleError(func(s *adler.Session, err error) { log.Println("ws error:", err) }) ``` -------------------------------- ### Handle Enter Key for Rename and Focus Message Input Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Listens for the 'Enter' key press on the name input. It prevents the default action, commits the rename, and then focuses the message input. ```javascript nameInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { event.preventDefault(); commitRename(); messageInput.focus(); } }); ``` -------------------------------- ### Group clients in rooms Source: https://github.com/catalinfl/adler/blob/master/README.md Enables organizing clients into logical groups called rooms for easier management and targeted communication. ```APIDOC ## Group clients in rooms Rooms help you manage subsets of sessions. ```go room := a.NewRoom("lobby") a.HandleConnect(func(s *adler.Session) { _ = room.Join(s) }) room.Broadcast([]byte("welcome to the lobby")) ``` Room helpers: - `NewRoom(name)` returns an existing room or creates one - `DeleteRoom(name)` removes a room manually when it is empty - `Name()` returns the room name - `Len()` returns the number of members - `Sessions()` returns a snapshot of current members - `Join(*Session)` adds a session to the room - `Leave(*Session)` removes a session - `OpenRoom()` allows joins again - `CloseRoom()` blocks new joins - `Broadcast`, `BroadcastBinary`, `BroadcastFilter`, `BroadcastJSON`, `BroadcastJSONFilter` ``` -------------------------------- ### Room Broadcast Helpers in Adler Source: https://context7.com/catalinfl/adler/llms.txt Provides various methods for broadcasting messages (text, binary, JSON) to all or filtered members within a room. Use these for efficient communication within a specific group of sessions. ```go room := a.NewRoom("channel") // Text to all room members room.Broadcast([]byte("hello room")) // Binary to all room members room.BroadcastBinary([]byte{0x01, 0x02}) // Text to filtered members room.BroadcastFilter([]byte("admins"), func(s *adler.Session) bool { role, _ := s.GetString("role") return role == "admin" }) // JSON to all room members _ = room.BroadcastJSON(adler.Map{"type": "update", "count": room.Len()}) // JSON to filtered members _ = room.BroadcastJSONFilter(adler.Map{"type": "secret"}, func(s *adler.Session) bool { level, _ := s.GetInt("clearance") return level >= 3 }) // Serializer-aware broadcast (JSON or Protobuf) _ = room.BroadcastAny(adler.Map{"event": "sync"}) ``` -------------------------------- ### Handle Room Button Clicks Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Attaches click event listeners to room buttons. When clicked, it sends a 'join_room' event with the target room name. ```javascript roomButtons.forEach((btn) => { btn.addEventListener('click', () => { const targetRoom = btn.dataset.room; if (!targetRoom || targetRoom === currentRoom) return; send({ type: 'join_room', room: targetRoom }); }); }); ``` -------------------------------- ### Manage Session Key-Value Store Source: https://context7.com/catalinfl/adler/llms.txt Utilize the concurrent-safe map for storing and retrieving session data. Typed accessors simplify type assertions, and atomic operations are available for integer values. ```go a.HandleConnect(func(s *adler.Session) { s.Set("role", "moderator") s.Set("score", int64(0)) s.SetNX("first_seen", time.Now().Unix()) // only sets if key absent; returns bool role, ok := s.GetString("role") // ("moderator", true) score, ok := s.GetInt64("score") // (0, true) flag, ok := s.GetBool("active") // ("", false) — key absent has := s.Has("role") // true // Atomic increment / decrement on *int64 values counter := int64(0) s.Set("hits", &counter) n, _ := s.Incr("hits") // n == 1 n, _ = s.Decr("hits") // n == 0 keys := s.Keys() // snapshot of key names vals := s.Values() // snapshot of values s.Unset("role") // remove one key s.Clear() // remove all keys _ = role; _ = score; _ = flag; _ = has; _ = n; _ = keys; _ = vals }) ``` -------------------------------- ### Adler Server Lifecycle Management Source: https://context7.com/catalinfl/adler/llms.txt Manage the Adler server lifecycle by checking session count, server status, and gracefully closing all sessions. Handles ErrCoreClosed if the server is already shut down. ```go a := adler.New() // Check active session count log.Println("sessions:", a.Len()) // Check whether the core has been shut down if a.IsClosed() { log.Println("server is closed") } // Gracefully close all sessions (queues a close frame for each) if err := a.Close(); err != nil { log.Println("close error:", err) // adler.ErrCoreClosed if already closed } ``` -------------------------------- ### Access Session Metadata Source: https://context7.com/catalinfl/adler/llms.txt Retrieve metadata associated with the current session, including the original HTTP request, protocol details, and network addresses. ```go a.HandleConnect(func(s *adler.Session) { r := s.Request() // original *http.Request proto := s.Protocol() // e.g. "HTTP/1.1" local := s.LocalAddr() // net.Addr of server-side socket remote := s.RemoteAddr() // net.Addr of client socket room := s.Room() // current *Room, or nil conn := s.UnsafeConn() // raw net.Conn — manage races yourself _ = r; _ = proto; _ = local; _ = remote; _ = room; _ = conn }) ``` -------------------------------- ### Serve WebSocket Endpoint with HandleRequest Source: https://github.com/catalinfl/adler/blob/master/README.md Integrate Adler into an HTTP server by registering a handler for the WebSocket endpoint. HandleRequest upgrades the connection and manages the session lifecycle. ```go http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { _ = a.HandleRequest(w, r) }) ``` -------------------------------- ### Upgrade HTTP to WebSocket with Adler Source: https://context7.com/catalinfl/adler/llms.txt Upgrades an HTTP connection to a WebSocket, registers the session, and initiates the session lifecycle. This function blocks until the session ends. Ensure to register handlers for connection events. ```go package main import ( "log" "net/http" "github.com/catalinfl/adler" ) func main() { a := adler.New() a.HandleConnect(func(s *adler.Session) { log.Println("new connection from", s.RemoteAddr()) }) a.HandleError(func(s *adler.Session, err error) { log.Println("error:", err) }) http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { if err := a.HandleRequest(w, r); err != nil { log.Println("upgrade failed:", err) } }) log.Fatal(http.ListenAndServe(":8080", nil)) } ``` -------------------------------- ### Session Messaging Operations Source: https://github.com/catalinfl/adler/blob/master/README.md Details the methods available on a `Session` object for sending messages to clients. ```APIDOC ### Session Messaging Helpers - `WriteText([]byte)` - `WriteTextWithDeadline([]byte, time.Duration)` - `WriteBinary([]byte)` - `WriteBinaryWithDeadline([]byte, time.Duration)` - `WriteJSON(adler.Map)` - `WriteJSONWithDeadline(any, time.Duration)` - `Close(...[]byte)` ``` -------------------------------- ### Handle Close Events Source: https://github.com/catalinfl/adler/blob/master/README.md Defines how to handle client disconnections, receiving the close status code and reason. ```APIDOC ## Close handling If the client sends a close frame, `HandleClose` receives the close status code and reason. ```go a.HandleClose(func(s *adler.Session, code int, reason string) { log.Printf("close: code=%d reason=%q", code, reason) }) ``` ``` -------------------------------- ### Broadcast to all or some clients Source: https://github.com/catalinfl/adler/blob/master/README.md Provides methods for sending messages to multiple connected clients, either to all clients or to a filtered subset. ```APIDOC ## Broadcast to all or some clients Use server-level broadcast helpers when you want to send to multiple sessions. - `Broadcast([]byte)` sends text to all connected sessions - `BroadcastFilter([]byte, func(*Session) bool)` sends text to matching sessions - `BroadcastBinary([]byte)` sends binary to all connected sessions - `BroadcastBinaryFilter([]byte, func(*Session) bool)` sends binary to matching sessions - `BroadcastJSON(adler.Map)` broadcasts JSON - `BroadcastJSONFilter(adler.Map, func(*Session) bool)` broadcasts JSON to matching sessions - `BroadcastOthers([]byte, *Session)` sends to everyone except the target session - `SendTo([]byte, *Session)` sends only to one session ### Example ```go a.Broadcast([]byte("server says hello")) a.SendTo([]byte("private message"), session) a.BroadcastFilter([]byte("admins only"), func(s *adler.Session) bool { role, _ := s.GetString("role") return role == "admin" }) ``` ``` -------------------------------- ### Handle Client Close Events Source: https://github.com/catalinfl/adler/blob/master/README.md The `HandleClose` function is called when a client sends a close frame. It receives the close status code and a reason string for logging or further processing. ```go a.HandleClose(func(s *adler.Session, code int, reason string) { log.Printf("close: code=%d reason=%q", code, reason) }) ``` -------------------------------- ### Session Lifecycle Handlers Source: https://context7.com/catalinfl/adler/llms.txt Callbacks to manage the lifecycle of a WebSocket session. These handlers are invoked at various stages, including connection, disconnection, message reception, and errors. ```APIDOC ## Session Lifecycle Handlers ### Description Register callbacks for the full session lifecycle. All callbacks receive a `*Session` as their first argument. ### Handlers - `HandleConnect`: Called after a session is successfully registered. - `HandleDisconnect`: Called after a session ends. - `HandleMessage`: Called on each inbound text frame. - `HandleMessageBinary`: Called on each inbound binary frame. - `HandleClose`: Called on receiving a client close frame, with code and reason. - `HandlePong`: Called on receiving a pong frame. - `HandleSentMessage`: Called after a text message is successfully sent. - `HandleSentMessageBinary`: Called after a binary message is successfully sent. - `HandleError`: Called when a session-level error occurs. - `OnRoomJoin`: Called after any session joins a room. - `OnRoomLeave`: Called after any session leaves a room. ``` -------------------------------- ### Session Metadata Access Source: https://github.com/catalinfl/adler/blob/master/README.md Describes how to access metadata associated with a session, such as the original request and connection details. ```APIDOC ### Session Metadata - `Request()` returns the original HTTP request - `Protocol()` returns the HTTP protocol string used during upgrade - `LocalAddr()` and `RemoteAddr()` expose the connection addresses - `Room()` returns the current room - `UnsafeConn()` exposes the raw network connection when you need low-level control ``` -------------------------------- ### adler.HandleRequest — Upgrade HTTP to WebSocket Source: https://context7.com/catalinfl/adler/llms.txt Upgrades an incoming HTTP connection to a WebSocket connection. It registers the new session, triggers the `HandleConnect` callback, and manages the session lifecycle until it ends. ```APIDOC ## adler.HandleRequest ### Description Upgrades an `http.ResponseWriter` / `*http.Request` pair to a WebSocket connection, registers the resulting session, fires `HandleConnect`, and blocks until the session ends. ### Method `func (a *Adler) HandleRequest(w http.ResponseWriter, r *http.Request) error` ### Parameters - `w` (`http.ResponseWriter`): The HTTP response writer. - `r` (`*http.Request`): The incoming HTTP request. ### Returns - `error`: An error if the upgrade process fails. ``` -------------------------------- ### Set Current Room Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Updates the currently selected room, updates the room label in the UI, and highlights the active room button. ```javascript function setCurrentRoom(nextRoom) { currentRoom = nextRoom; roomLabel.textContent = nextRoom; roomButtons.forEach((btn) => { btn.classList.toggle('active', btn.dataset.room === nextRoom); }); } ``` -------------------------------- ### Server-Level Broadcast Operations Source: https://context7.com/catalinfl/adler/llms.txt Details how to send messages to multiple sessions simultaneously using server-level broadcast functions, with options for filtering recipients. ```APIDOC ## Server-Level Broadcast Operations ### Description Send a message to many sessions at once using server-level helpers. ### Methods - `Broadcast([]byte)`: Sends text to every connected session. - `BroadcastFilter([]byte, func(*Session) bool)`: Sends text to a filtered subset of sessions. - `BroadcastBinary([]byte)`: Sends binary data to all sessions. - `BroadcastBinaryFilter([]byte, func(*Session) bool)`: Sends binary data to a filtered subset. - `BroadcastJSON(any)`: Broadcasts JSON data to all sessions. - `BroadcastJSONFilter(any, func(*Session) bool)`: Broadcasts JSON data to a filtered subset. - `BroadcastAny(any)`: Sends serializer-aware payload to all sessions. - `BroadcastOthers([]byte, *Session)`: Sends a message to all sessions except the triggering session. - `SendTo([]byte, *Session)`: Sends a message to a single named session. - `Len()`: Returns the number of active sessions. ``` -------------------------------- ### Server-Level Broadcast Messages Source: https://context7.com/catalinfl/adler/llms.txt Send messages to all connected sessions or a filtered subset using various broadcast helpers. Supports text, binary, JSON, and serializer-aware payloads. ```go // Send text to every connected session _ = a.Broadcast([]byte("server announcement")) // Send to a filtered subset _ = a.BroadcastFilter([]byte("moderators only"), func(s *adler.Session) bool { role, _ := s.GetString("role") return role == "moderator" }) // Send binary to all _ = a.BroadcastBinary([]byte{0xDE, 0xAD, 0xBE, 0xEF}) // Send binary to filtered subset _ = a.BroadcastBinaryFilter([]byte{0x01}, func(s *adler.Session) bool { return s.Has("binary_capable") }) // Broadcast JSON map _ = a.BroadcastJSON(adler.Map{"event": "tick", "t": time.Now().Unix()}) // Broadcast JSON to subset _ = a.BroadcastJSONFilter(adler.Map{"event": "vip"}, func(s *adler.Session) bool { vip, _ := s.GetBool("vip") return vip }) // Send serializer-aware payload to all (JSON or Protobuf depending on WithProtocol) _ = a.BroadcastAny(adler.Map{"event": "ping"}) // Send to everyone except the triggering session (relay pattern) a.HandleMessage(func(s *adler.Session, msg []byte) { _ = a.BroadcastOthers(msg, s) }) // Send only to one named session var target *adler.Session // obtained from session store or a registry if target != nil { _ = a.SendTo([]byte("private"), target) } log.Println("active sessions:", a.Len()) ``` -------------------------------- ### Session Metadata Accessors Source: https://context7.com/catalinfl/adler/llms.txt Provides methods to access metadata associated with a session, such as the original HTTP request, protocol, local/remote addresses, and the underlying connection. ```APIDOC ## Session Metadata Accessors ### Description Provides access to metadata associated with the session. ### Methods - `Request()`: Returns the original `*http.Request`. - `Protocol()`: Returns the protocol string (e.g., "HTTP/1.1"). - `LocalAddr()`: Returns the server-side socket's `net.Addr`. - `RemoteAddr()`: Returns the client socket's `net.Addr`. - `Room()`: Returns the current `*Room`, or `nil`. - `UnsafeConn()`: Returns the raw `net.Conn` (use with caution). ``` -------------------------------- ### Send Event via WebSocket Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Serializes an event object to JSON and sends it over the WebSocket connection if it is open. Returns true if sent, false otherwise. ```javascript function send(event) { if (!socket || socket.readyState !== WebSocket.OPEN) return false; socket.send(JSON.stringify(event)); return true; } ``` -------------------------------- ### Commit User Rename Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Handles the logic for renaming the current user. It trims whitespace from the input and sends a 'rename' event if the name has changed. ```javascript function commitRename() { const nextName = nameInput.value.trim(); if (!nextName || nextName === currentName) return; setCurrentName(nextName); send({ type: 'rename', name: nextName }); } ``` -------------------------------- ### Set Current User Name Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Updates the current user's name, updates the UI label, and persists the name to local storage. ```javascript function setCurrentName(nextName) { currentName = nextName; currentNameLabel.textContent = nextName || 'guest'; localStorage.setItem('adler-name', nextName); } ``` -------------------------------- ### Display Message in Chat Source: https://github.com/catalinfl/adler/blob/master/examples/chat/index.html Appends a new message to the chat interface. Handles system messages and regular user messages, including sender information and escaping HTML content. ```javascript function pushMessage(payload, me = false) { const { type, from, message } = payload; const li = document.createElement('li'); li.className = 'message'; if (type === 'system') { li.classList.add('system'); li.innerHTML = `