socketio

package
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package socketio implements the Socket.IO v5 protocol layer on top of the engineio transport package.

Protocol reference: https://socket.io/docs/v4/socket-io-protocol/

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNamespaceNotConnected is returned when emitting on a namespace the
	// client is not connected to. It wraps ErrNotConnected so callers can
	// match either the namespace-level or the base sentinel with errors.Is.
	ErrNamespaceNotConnected = fmt.Errorf("%w: namespace not connected", ErrNotConnected)

	// ErrHandlerMismatch is returned when an event handler's signature does
	// not match the event's arguments.
	ErrHandlerMismatch = errors.New("socketio: handler signature mismatch")
)

sentinel errors exposed to users.

View Source
var (
	// ErrNotConnected is returned when emitting on a disconnected socket.
	ErrNotConnected = errors.New("socketio: socket is not connected")
	// ErrInvalidNamespace is returned when broadcasting to an unknown namespace.
	ErrInvalidNamespace = errors.New("socketio: invalid namespace")
)

sentinel errors exposed to users.

View Source
var NopLogger engineio.Logger = slogLogger{/* contains filtered or unexported fields */}

NopLogger discards all log output.

Functions

This section is empty.

Types

type Adapter added in v1.1.0

type Adapter interface {
	// AddSocket registers an id and automatically joins it to a room named
	// after the id itself.
	AddSocket(id string)
	// RemoveSocket removes the id from the adapter and from every room.
	RemoveSocket(id string)
	// AddToRoom joins an id to a room.
	AddToRoom(room, id string)
	// RemoveFromRoom leaves an id from a room.
	RemoveFromRoom(room, id string)
	// Broadcast invokes deliver once per target id. An empty room means the
	// whole namespace; ids listed in except are skipped; an unknown room
	// delivers to nobody. deliver is called outside any adapter lock.
	Broadcast(room string, except []string, deliver func(id string))
	// Sockets returns the current socket ids. The adapter is authoritative
	// for membership.
	Sockets() []string
	// SocketsCount returns the number of current socket ids.
	SocketsCount() int
	// Close releases adapter resources. It must be idempotent.
	Close() error
}

Adapter is the pluggable room and membership backend of a Socket.IO namespace. It is id-driven and knows nothing about *Socket: the namespace resolves ids to sockets when delivering. A custom Adapter (e.g. one backed by Redis) can be injected by the server; NewMemoryAdapter is the default in-process implementation.

Lock discipline: Broadcast must invoke deliver only after it released its internal lock — the namespace's addSocket path takes the namespace lock and then calls adapter methods, so an adapter that delivers under its own lock deadlocks with that path.

func NewMemoryAdapter added in v1.1.0

func NewMemoryAdapter() Adapter

NewMemoryAdapter returns the default in-process Adapter: an id set, a room-to-ids map and a single RWMutex protecting both.

type AdapterFactory added in v1.1.0

type AdapterFactory func(nsp string) Adapter

AdapterFactory creates the Adapter for a namespace, typically keyed by the namespace name so each namespace gets its own state.

type BroadcastOperator

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

BroadcastOperator targets a subset of a namespace for broadcasting.

func (*BroadcastOperator) Emit

func (b *BroadcastOperator) Emit(event string, args ...any)

Emit sends the event to the targeted sockets.

func (*BroadcastOperator) Except added in v1.1.0

func (b *BroadcastOperator) Except(ids ...string) *BroadcastOperator

Except returns a new operator that also excludes the listed ids, chainable before Emit. The receiver is left unchanged.

type CORSConfig added in v1.1.0

type CORSConfig struct {
	AllowAll       bool
	AllowedOrigins []string
}

CORSConfig describes the cross-origin access rules of a server. AllowAll permits every origin; AllowedOrigins lists the exact origins that are permitted.

func AllowAll added in v1.1.0

func AllowAll() *CORSConfig

AllowAll returns a CORSConfig that permits every origin.

type Client

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

Client is a Socket.IO client. It maintains one Engine.IO connection multiplexing any number of namespaces, and mirrors the official socket.io-client behaviour for events, acknowledgements, binary payloads and reconnection.

func Dial

func Dial(ctx context.Context, rawURL string, opts *Options) (*Client, error)

Dial connects to a Socket.IO server. It blocks until the root namespace CONNECT completes or ctx is cancelled.

Example

ExampleDial demonstrates a full Socket.IO round trip: start a server, connect a client, emit an event and receive the acknowledgement. The handler's return value becomes the ack payload.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http/httptest"
	"time"

	"github.com/kingecg/gosocketio/socketio"
)

func main() {
	srv := socketio.NewServer(nil)
	srv.OnEvent("/", "ping", func(s *socketio.Socket) string {
		return "pong"
	})
	httpSrv := httptest.NewServer(srv)
	defer httpSrv.Close()

	c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	ack := make(chan []any, 1)
	if _, err := c.EmitWithAck("/", "ping", func(args []any) {
		ack <- args
	}); err != nil {
		log.Fatal(err)
	}
	select {
	case args := <-ack:
		fmt.Printf("ack: %v\n", args)
	case <-time.After(5 * time.Second):
		log.Fatal("timed out waiting for ack")
	}
}
Output:
ack: [pong]

func (*Client) Close

func (c *Client) Close() error

Close terminates the session. Pending acknowledgements are dropped and every connected namespace reports a disconnect with reason "io client disconnect".

func (*Client) ConnectNamespace

func (c *Client) ConnectNamespace(ctx context.Context, nsp string, data map[string]any) error

ConnectNamespace connects an additional namespace, blocking until the CONNECT acknowledgement arrives or ctx is cancelled.

func (*Client) Connected

func (c *Client) Connected(nsp string) bool

Connected reports whether the namespace is currently connected.

func (*Client) DisconnectNamespace

func (c *Client) DisconnectNamespace(nsp string)

DisconnectNamespace disconnects a namespace, notifying the server.

func (*Client) Emit

func (c *Client) Emit(nsp, event string, args ...any) error

Emit sends an event on a namespace without an acknowledgement.

func (*Client) EmitWithAck

func (c *Client) EmitWithAck(nsp, event string, cb func(args []any), args ...any) (int64, error)

EmitWithAck sends an event that requires an acknowledgement. cb is invoked with the server's reply once it arrives.

func (*Client) ID

func (c *Client) ID(nsp string) string

ID returns the namespace-scoped socket id assigned by the server.

func (*Client) OnConnect

func (c *Client) OnConnect(nsp string, f func())

OnConnect registers a handler invoked when a namespace connection completes. It fires again after each successful reconnection.

func (*Client) OnConnectError

func (c *Client) OnConnectError(nsp string, f func(err error))

OnConnectError registers a handler invoked when a namespace connection is rejected, carrying the error reported by the server.

func (*Client) OnDisconnect

func (c *Client) OnDisconnect(nsp string, f func(reason string))

OnDisconnect registers a handler invoked when a namespace disconnects, carrying the Socket.IO disconnect reason.

func (*Client) OnError added in v1.1.0

func (c *Client) OnError(nsp string, f func(err error))

OnError registers a handler invoked when an event handler fails to dispatch (for example an argument that cannot be decoded into the handler's parameter type), carrying the dispatch error. It is distinct from OnConnectError and never fires for connect, disconnect or connect_error paths.

func (*Client) OnEvent

func (c *Client) OnEvent(nsp, event string, f any)

OnEvent registers a handler invoked when an event is received on a namespace. Handler return values form the acknowledgement payload when the server requested one.

func (*Client) SetLogger

func (c *Client) SetLogger(l engineio.Logger)

SetLogger sets the client logger.

type Middleware

type Middleware func(s *Socket, data map[string]any) error

Middleware is invoked with the CONNECT payload before a connection to the namespace is accepted. Returning a non-nil error rejects the connection with a CONNECT_ERROR packet carrying the error message.

type Options

type Options struct {
	// Transports overrides the Engine.IO transports.
	Transports []string

	// Engine passes extra options to engineio.Dial.
	Engine *engineio.Options

	// Auth is sent as the root namespace CONNECT payload.
	Auth map[string]any

	// Reconnection enables automatic reconnection after an unexpected
	// close of the underlying transport.
	Reconnection bool

	// ReconnectionAttempts limits consecutive reconnection attempts. Zero
	// (the default) means retry forever.
	ReconnectionAttempts int

	// ReconnectionDelay is the base delay before the first reconnection
	// attempt. Default: 1s.
	ReconnectionDelay time.Duration

	// ReconnectionDelayMax caps the exponentially backed-off delay.
	// Default: 5s.
	ReconnectionDelayMax time.Duration

	// RandomizationFactor in [0,1] jitters the delay so simultaneous
	// clients do not reconnect in lockstep. Default: 0.5.
	RandomizationFactor float64

	// Timeout bounds each reconnection attempt (Engine.IO dial plus all
	// namespace CONNECTs). Default: 10s.
	Timeout time.Duration
}

Options configures a Socket.IO client session.

type Packet

type Packet struct {
	Type PacketType
	Nsp  string
	ID   int64
	Data any

	// Attachments is the number of binary buffers following a binary
	// packet. It is only meaningful for BinaryEvent/BinaryAck packets.
	Attachments int
}

Packet is a Socket.IO packet. Data holds the decoded JSON payload:

  • Connect / ConnectError: map[string]any (or nil)
  • Event: []any ([eventName, args...])
  • Ack: []any ([args...])

A negative ID means the packet carries no acknowledgement id.

func Decode

func Decode(data []byte) (*Packet, error)

Decode parses a text Socket.IO packet.

func (*Packet) Encode

func (p *Packet) Encode() []byte

Encode serializes the packet to its text form. Binary packets must have already been deconstructed into placeholder form (see deconstruct).

type PacketType

type PacketType byte

PacketType is the Socket.IO packet type.

const (
	Connect      PacketType = '0'
	Disconnect   PacketType = '1'
	Event        PacketType = '2'
	Ack          PacketType = '3'
	ConnectError PacketType = '4'
	BinaryEvent  PacketType = '5'
	BinaryAck    PacketType = '6'
)

Socket.IO packet types.

func (PacketType) String

func (t PacketType) String() string

String returns the packet type name.

type Server

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

Server is a Socket.IO server layered on top of an Engine.IO server. It implements http.Handler.

func NewServer

func NewServer(opts *engineio.Options) *Server

NewServer creates a Socket.IO server with its own Engine.IO server. opts customizes the underlying Engine.IO layer.

func NewServerWithConfig added in v1.1.0

func NewServerWithConfig(cfg *ServerConfig) *Server

NewServerWithConfig creates a Socket.IO server from a ServerConfig. A nil config or a zero-value config behaves exactly like NewServer(nil).

func (*Server) BroadcastToNamespace

func (s *Server) BroadcastToNamespace(nsp, event string, args ...any)

BroadcastToNamespace sends an event to every socket in a namespace.

func (*Server) BroadcastToRoom

func (s *Server) BroadcastToRoom(nsp, room, event string, args ...any)

BroadcastToRoom sends an event to every socket in a namespace's room.

Example

ExampleServer_BroadcastToRoom demonstrates room-scoped broadcasting: every socket that joined the room receives the message (including the sender), while sockets outside the room do not.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http/httptest"
	"sort"
	"time"

	"github.com/kingecg/gosocketio/socketio"
)

func main() {
	srv := socketio.NewServer(nil)
	srv.OnEvent("/", "join", func(s *socketio.Socket, room string) string {
		s.JoinRoom(room)
		return "joined"
	})
	srv.OnEvent("/", "room message", func(s *socketio.Socket, text string) {
		srv.BroadcastToRoom("/", "lobby", "room message", text)
	})
	httpSrv := httptest.NewServer(srv)
	defer httpSrv.Close()

	connect := func() *socketio.Client {
		c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
		if err != nil {
			log.Fatal(err)
		}
		return c
	}
	alice := connect()
	defer alice.Close()
	bob := connect()
	defer bob.Close()

	// The server acknowledges each join, so membership is guaranteed before
	// the broadcast happens.
	join := func(c *socketio.Client) {
		done := make(chan []any, 1)
		if _, err := c.EmitWithAck("/", "join", func(args []any) { done <- args }, "lobby"); err != nil {
			log.Fatal(err)
		}
		select {
		case <-done:
		case <-time.After(5 * time.Second):
			log.Fatal("timed out waiting for join ack")
		}
	}
	join(alice)
	join(bob)

	// Carol connects but never joins the room.
	carol := connect()
	defer carol.Close()
	carolGot := make(chan struct{})
	carol.OnEvent("/", "room message", func(text string) {
		close(carolGot)
	})

	got := make(chan string, 2)
	alice.OnEvent("/", "room message", func(text string) { got <- "alice: " + text })
	bob.OnEvent("/", "room message", func(text string) { got <- "bob: " + text })

	if err := alice.Emit("/", "room message", "hello lobby"); err != nil {
		log.Fatal(err)
	}
	msgs := make([]string, 0, 2)
	for i := 0; i < 2; i++ {
		select {
		case m := <-got:
			msgs = append(msgs, m)
		case <-time.After(5 * time.Second):
			log.Fatal("timed out waiting for room broadcast")
		}
	}
	// Sort ensures deterministic output regardless of receive order.
	sort.Strings(msgs)
	for _, m := range msgs {
		fmt.Println(m)
	}
	select {
	case <-carolGot:
		log.Fatal("carol received a message despite not being in the room")
	default:
		fmt.Println("carol: no message (not in the room)")
	}
}
Output:
alice: hello lobby
bob: hello lobby
carol: no message (not in the room)

func (*Server) Close

func (s *Server) Close()

Close closes all sessions and releases every namespace's adapter.

func (*Server) Engine

func (s *Server) Engine() *engineio.Server

Engine returns the underlying Engine.IO server.

func (*Server) Namespace

func (s *Server) Namespace(nsp string) *namespace

Namespace returns the namespace, creating it on first access.

func (*Server) OnAny added in v1.1.0

func (s *Server) OnAny(nsp string, f func(*Socket, string, []any))

OnAny registers a handler invoked for every event a client emits in the namespace, before the named event handlers dispatch. The handler receives the socket, the event name and the decoded arguments. It fires for every EVENT packet — including events with no registered handler — and never for connect, disconnect, connect_error or acknowledgement packets.

func (*Server) OnConnect

func (s *Server) OnConnect(nsp string, f func(*Socket) error)

OnConnect registers a handler invoked when a client connects to a namespace. Returning an error rejects the connection with a CONNECT_ERROR packet.

func (*Server) OnDisconnect

func (s *Server) OnDisconnect(nsp string, f func(*Socket, string))

OnDisconnect registers a handler invoked when a socket leaves a namespace.

func (*Server) OnError added in v1.1.0

func (s *Server) OnError(nsp string, f func(*Socket, error))

OnError registers a handler invoked when an event handler fails to dispatch (for example an argument that cannot be decoded into the handler's parameter type), carrying the socket and the dispatch error. It never fires for connect, disconnect or connect_error paths.

func (*Server) OnEvent

func (s *Server) OnEvent(nsp, event string, f any)

OnEvent registers a handler for an event within a namespace.

Example

ExampleServer_OnEvent demonstrates a server-side event handler receiving typed arguments. []byte arguments travel as binary attachments and are reconstructed before the handler is invoked.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http/httptest"
	"time"

	"github.com/kingecg/gosocketio/socketio"
)

func main() {
	srv := socketio.NewServer(nil)
	srv.OnEvent("/", "download", func(s *socketio.Socket, name string, data []byte) {
		fmt.Printf("server received %q (%d bytes)\n", name, len(data))
		s.Emit("file", name, data)
	})
	httpSrv := httptest.NewServer(srv)
	defer httpSrv.Close()

	c, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	got := make(chan struct{})
	c.OnEvent("/", "file", func(name string, data []byte) {
		fmt.Printf("client received %q (%d bytes)\n", name, len(data))
		close(got)
	})
	if err := c.Emit("/", "download", "logo.png", []byte{0x89, 0x50, 0x4e, 0x47}); err != nil {
		log.Fatal(err)
	}
	select {
	case <-got:
	case <-time.After(5 * time.Second):
		log.Fatal("timed out waiting for binary echo")
	}
}
Output:
server received "logo.png" (4 bytes)
client received "logo.png" (4 bytes)

func (*Server) RegisterNamespace added in v1.1.0

func (s *Server) RegisterNamespace(nsp string) error

RegisterNamespace explicitly pre-registers a namespace so handlers can be attached before any client connects. It is idempotent: registering an already-created namespace returns nil. The namespace must start with "/" and be longer than one character; the empty string and "/" are rejected with a descriptive error. Implicit on-demand creation of unregistered namespaces is unchanged.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. When the server has a configured path, requests outside that prefix are rejected with 404 before delegating to the Engine.IO server. When CORS is configured, preflight and origin checks run after the path guard and before the engine.

func (*Server) SetAdapterFactory added in v1.1.0

func (s *Server) SetAdapterFactory(f AdapterFactory)

SetAdapterFactory sets the factory used to create the Adapter of namespaces created after this call. It is ignored when a factory was already configured through ServerConfig.Adapter. A nil factory selects the in-memory adapter. Namespaces created before this call keep their existing adapter.

func (*Server) SetLogger

func (s *Server) SetLogger(l engineio.Logger)

SetLogger sets the server logger.

func (*Server) SetUnknownEventAck added in v1.2.0

func (s *Server) SetUnknownEventAck(nsp string, data []any)

SetUnknownEventAck sets the acknowledgement payload returned for EVENT packets that carry an ack id but match no registered handler in the namespace. nil (the default) keeps the historical behaviour of acknowledging with an empty payload.

func (*Server) ToExcept added in v1.1.0

func (s *Server) ToExcept(nsp, room string, except []string, event string, args ...any) error

ToExcept sends an event to every socket in a namespace's room except the listed ids. An empty except list delivers to every member, matching BroadcastToRoom. It returns an error when the namespace does not exist.

func (*Server) Use

func (s *Server) Use(nsp string, m Middleware)

Use registers a connection middleware for a namespace.

Example

ExampleServer_Use demonstrates a connection middleware that validates the CONNECT payload (for example an auth token) and rejects unauthorized clients with a CONNECT_ERROR.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http/httptest"

	"github.com/kingecg/gosocketio/socketio"
)

func main() {
	srv := socketio.NewServer(nil)
	srv.Use("/", func(s *socketio.Socket, data map[string]any) error {
		if token, _ := data["token"].(string); token != "secret" {
			return errors.New("unauthorized")
		}
		return nil
	})
	httpSrv := httptest.NewServer(srv)
	defer httpSrv.Close()

	good, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", &socketio.Options{
		Auth: map[string]any{"token": "secret"},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("authorized: connected")
	good.Close()

	if _, err := socketio.Dial(context.Background(), httpSrv.URL+"/socket.io/", &socketio.Options{
		Auth: map[string]any{"token": "wrong"},
	}); err != nil {
		fmt.Println("unauthorized:", err)
	}
}
Output:
authorized: connected
unauthorized: unauthorized

type ServerConfig added in v1.1.0

type ServerConfig struct {
	// Engine customizes the underlying Engine.IO server. nil selects the
	// Engine.IO defaults.
	Engine *engineio.Options

	// Path restricts requests to those whose URL path has this prefix. The
	// empty string disables the guard, matching the historical behavior of
	// NewServer.
	Path string

	// CORS configures cross-origin access. nil disables CORS handling; the
	// behavior itself is implemented by the CORS layer.
	CORS *CORSConfig

	// Adapter creates the room and membership backend for each namespace. nil
	// selects the in-memory adapter.
	Adapter AdapterFactory
}

ServerConfig configures a Socket.IO server.

type Socket

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

Socket is a single Socket.IO connection to a namespace. It is created when a client sends a CONNECT packet for the namespace and stays valid until the client or server disconnects that namespace.

func (*Socket) Broadcast

func (s *Socket) Broadcast(event string, args ...any)

Broadcast sends an event to every socket in the namespace except this one.

func (*Socket) BroadcastToRoom

func (s *Socket) BroadcastToRoom(room, event string, args ...any)

BroadcastToRoom sends an event to every socket in room except this one.

func (*Socket) Connected

func (s *Socket) Connected() bool

Connected reports whether the socket is still connected.

func (*Socket) Disconnect

func (s *Socket) Disconnect()

Disconnect disconnects this namespace socket, notifying the client.

func (*Socket) Emit

func (s *Socket) Emit(event string, args ...any) error

Emit sends an event to this socket.

func (*Socket) EmitWithAck

func (s *Socket) EmitWithAck(event string, cb func(args []any), args ...any) (int64, error)

EmitWithAck sends an event that requires an acknowledgement. cb is invoked with the client's reply once it arrives.

func (*Socket) Engine

func (s *Socket) Engine() *engineio.Socket

Engine returns the underlying Engine.IO connection.

func (*Socket) ID

func (s *Socket) ID() string

ID returns the namespace-scoped socket id.

func (*Socket) JoinRoom

func (s *Socket) JoinRoom(room string)

JoinRoom subscribes the socket to a room.

func (*Socket) LeaveRoom

func (s *Socket) LeaveRoom(room string)

LeaveRoom unsubscribes the socket from a room.

func (*Socket) Nsp

func (s *Socket) Nsp() string

Nsp returns the namespace this socket is connected to.

func (*Socket) RemoteAddr

func (s *Socket) RemoteAddr() string

RemoteAddr returns the remote address of the underlying connection.

func (*Socket) To

func (s *Socket) To(room string) *BroadcastOperator

To returns a broadcast operator targeting a single room.

func (*Socket) ToExcept added in v1.1.0

func (s *Socket) ToExcept(room string, except []string) *BroadcastOperator

ToExcept returns a broadcast operator targeting a room, excluding the sender and the listed ids.

Jump to

Keyboard shortcuts

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