wsrooms

package module
v0.0.0-...-0aacc87 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2025 License: MIT Imports: 10 Imported by: 0

README

wsrooms – Room-Based WebSocket Framework

A lightweight, high-performance WebSocket framework for real-time applications in Go (server) and JavaScript (client). Built around rooms, binary framing, and explicit message routing, wsrooms handles connection lifecycle, room membership, and concurrency so you don’t have to.


✅ Key Features

  • 🏢 Automatic Room Management: Create/join/leave rooms on demand.
  • Efficient Binary Protocol: Uses length-prefixed fields for compact, fast message encoding.
  • 📨 Flexible Messaging:
    • Broadcast to rooms (excluding sender)
    • Send direct messages to peers
    • Send private messages to self via TrySend
  • 🔒 Concurrency-Safe: Thread-safe rooms and hub using Go’s sync primitives.
  • 🧩 Handler Registration: Register per-event logic on the server with RegisterHandler.
  • 🌐 Single Root Connection: Clients start in a "root" room and dynamically join others.
  • 📦 Minimal Dependencies: Only gorilla/websocket (Go) and standard JS.

📦 Installation

Go Server
go get github.com/joncody/wsrooms
JavaScript Client

Include these files in your frontend:

  • wsrooms.js
  • bytecursor.js (for binary parsing)
  • emitter.js (optional, if using event emitter pattern)
import wsrooms from './wsrooms.js';

🧠 Quick Start

1. Go Server
package main

import (
	"log"
	"net/http"
	"github.com/joncody/wsrooms"
)

func main() {
	// Register custom event handler
	err := wsrooms.RegisterHandler("ping", func(c *wsrooms.Conn, msg *wsrooms.Message) error {
		// Respond directly to the sender
		reply := wsrooms.NewMessage("util", "pong", "", c.ID, nil)
		c.TrySend(reply.Bytes())
		return nil
	})
	if err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/ws", wsrooms.SocketHandler(nil))
	http.ListenAndServe(":8080", nil)
}
2. JavaScript Client
"use strict";

import wsrooms from "./wsrooms.js";

const decoder = new TextDecoder();
const root = wsrooms("ws://localhost:8080/ws");

root.on("open", () => {
    console.log("Connected! My ID:", root.id());
	const lobby = root.join("lobby");
	lobby.on("open", () => {
		lobby.send("ping", new Uint8Array());
	});
	lobby.on("pong", (payload, senderId) => {
		console.log("Received pong from", senderId);
	});
	lobby.on("new_member", (id) => {
        console.log(`User joined: ${id}`);
	});
    lobby.on("member_left", (id) => {
        console.log(`User left: ${id}`);
    });
});

📚 Client API (wsrooms.js)

Initialization
const root = wsrooms("ws://...");

Returns the root room. All other rooms are created via .join().

Room Methods
Method Description
.join(name) Joins a room; returns a frozen Room object.
.leave() Leaves the room and cleans up listeners.
.send(event, payload, [dst]) Sends a message (to room or direct to dst).
.open() true if the room is active.
.members() Returns a deep copy of current member IDs.
.id() Returns your client ID in this room.
Events

Use .on(event, handler) to listen:

  • "open" — room joined successfully
  • "close" — room left or connection closed
  • "new_member"(memberId) when someone joins
  • "member_left"(memberId) when someone leaves
  • Custom events (e.g., "chat") — (payload, senderId)

⚠️ Reserved event names (join, leave, join_ack, etc.) cannot be used for custom messages.


📚 Server API (wsrooms Go package)

Core Functions
Function Description
RegisterHandler(event string, handler func(*Conn, *Message) error) Registers a custom message handler. Returns error if duplicate or invalid.
SocketHandler(auth Authorize) Returns an http.HandlerFunc. Optional auth function extracts claims from request.
*Conn Methods
Method Description
SendToRoom(room, event string, payload []byte) Broadcasts to all room members except sender.
SendToClient(dstID, event string, payload []byte) Sends direct message to another client (uses "root" room internally).
TrySend(msg []byte) bool Sends a message to self (e.g., acks, replies). Non-blocking; returns false if client is slow/disconnected.
ID Unique connection UUID (read-only field).
Claims Map of auth claims (e.g., from JWT).
Message Utilities
Function Description
NewMessage(room, event, dst, src string, payload []byte) *Message Builds a message struct.
BytesToMessage([]byte) *Message Decodes binary message (used internally).

The server automatically handles "join"/"leave" events. Custom events are routed to registered handlers or broadcast if unhandled.


📐 Message Protocol (Binary)

Each message is a sequence of length-prefixed fields (big-endian uint32):

  1. Room name (string)
  2. Event name (string)
  3. Destination ID (string, empty = broadcast)
  4. Source ID (string)
  5. Payload ([]byte)

Example:
[4][lobby][4][chat][0][][36][abc...][11][Hello room!]

Clients must send/receive binary WebSocket frames, not text.


🛡️ Concurrency & Safety

  • All room operations are goroutine-safe.
  • Connections use buffered channels + ping/pong to prevent hangs.
  • Non-blocking sends: TrySend and internal messaging never block.
  • Rooms auto-clean when empty.
  • Malformed or oversized messages are dropped.

📄 License

See LICENSE

Documentation

Overview

Package wsrooms provides a scalable, room-based WebSocket server for real-time bidirectional communication between clients.

Core Concepts

  • Connection (Conn): Represents a single authenticated WebSocket client. Each has a unique ID and optional claims (e.g., user ID, roles).

  • Room: A named group of connections. Messages sent to a room are broadcast to all members (excluding the sender). The "root" room is auto-joined by every new connection.

  • Message Format: Binary, length-prefixed frames encoding room, event, destination, source, and payload. This enables efficient parsing and low overhead.

  • Event Dispatch: Built-in events ("join", "leave") and custom events handled via registered MessageHandlers.

Usage

  1. Register custom event handlers (optional): wsrooms.RegisterHandler("chat", func(c *wsrooms.Conn, msg *wsrooms.Message) error { // Handle "chat" event return nil })

  2. Mount the WebSocket handler with optional authentication: http.Handle("/ws", wsrooms.SocketHandler(func(r *http.Request) (map[string]string, error) { // Extract JWT claims, session, etc. return claims, nil }))

  3. Message Structure and Construction

    All messages (client→server and server→client) follow a binary, length-prefixed format with these fields: - Room (string): Target room name (use "root" for direct messages). - Event (string): Event type (e.g., "join", "chat", "update"). - Dst (string): Optional destination client ID (for direct messages). - Src (string): Source client ID (auto-set by server on send). - Payload ([]byte): Arbitrary binary data (commonly JSON-encoded).

    To construct a message on the server, use NewMessage: msg := wsrooms.NewMessage( room, // e.g., "lobby" event, // e.g., "chat" dst, // e.g., "" for broadcast, or "abc123" for direct src, // typically c.ID (sender's ID) payload, // e.g., []byte(`{"text":"hello"}`) ) rawBytes := msg.Bytes() // serialize for sending

    Built-in client→server events: - "join": { "event": "join", "room": "lobby" } - "leave": { "event": "leave", "room": "lobby" } To send a direct message from client, set "dst" to the recipient's ID.

  4. Server-Side Messaging APIs

    From within a MessageHandler or server logic, use:

    - c.TrySend(msg []byte) bool Sends raw binary message; returns false if dropped (slow or closed client). Typically used with msg := NewMessage(...); c.TrySend(msg.Bytes()).

    - c.SendToRoom(room, event string, payload []byte) Broadcasts to all members of a room (excluding sender). Equivalent to: NewMessage(room, event, "", c.ID, payload) + room emit.

    - c.SendToClient(dstID, event string, payload []byte) Sends a direct message to another client by ID. Equivalent to: NewMessage("root", event, dstID, c.ID, payload) + direct send.

    Example: func chatHandler(c *wsrooms.Conn, msg *wsrooms.Message) error { // Echo message back as direct reply reply := wsrooms.NewMessage( "root", "echo", msg.Src, c.ID, []byte("ack"), ) if !c.TrySend(reply.Bytes()) { log.Printf("Failed to send reply to %s", msg.Src) } return nil }

Safety

  • All exported APIs are safe for concurrent use.
  • Connections auto-cleanup on disconnect, error, or write timeout.
  • Empty rooms are garbage-collected automatically.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterHandler

func RegisterHandler(event string, handler MessageHandler) error

RegisterHandler registers a custom event handler for a given event name.

func SocketHandler

func SocketHandler(authFn Authorize) http.HandlerFunc

SocketHandler returns an HTTP handler that upgrades to WebSocket and manages the connection lifecycle.

Types

type Authorize

type Authorize func(*http.Request) (map[string]string, error)

Authorize is a function that extracts authenticated claims from an HTTP request.

type Conn

type Conn struct {
	ID     string
	Claims map[string]string // Authenticated claims (e.g., user ID, roles)
	// contains filtered or unexported fields
}

Conn represents a single WebSocket connection with metadata and messaging channels.

func (*Conn) SendToClient

func (c *Conn) SendToClient(dstID, event string, payload []byte)

SendToClient sends a direct message to another client by ID.

func (*Conn) SendToRoom

func (c *Conn) SendToRoom(roomName, event string, payload []byte)

SendToRoom broadcasts a message to all members of the specified room.

func (*Conn) TrySend

func (c *Conn) TrySend(msg []byte) bool

TrySend attempts to send a message; drops it if the send buffer is full or closed.

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub manages all rooms and connections (singleton via global `hub`).

type Message

type Message struct {
	RoomLength    int
	Room          string
	EventLength   int
	Event         string
	DstLength     int
	Dst           string
	SrcLength     int
	Src           string
	PayloadLength int
	Payload       []byte
}

Message represents a length-prefixed binary message for efficient parsing.

func BytesToMessage

func BytesToMessage(data []byte) *Message

BytesToMessage decodes raw bytes into a Message (returns nil on malformed input).

func NewMessage

func NewMessage(room, event, dst, src string, payload []byte) *Message

NewMessage builds a new Message with computed length fields.

func (*Message) Bytes

func (msg *Message) Bytes() []byte

Bytes serializes the Message into a binary format with length prefixes.

type MessageHandler

type MessageHandler func(c *Conn, msg *Message) error

MessageHandler processes a custom event message from a connection.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL