socketio

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 16 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 (
	// ErrNotConnected is returned when emitting on a disconnected socket.
	ErrNotConnected = errors.New("socketio: socket is not connected")
)

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 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.

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) 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 (*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"
	"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)
	}
	for i := 0; i < 2; i++ {
		select {
		case m := <-got:
			fmt.Println(m)
		case <-time.After(5 * time.Second):
			log.Fatal("timed out waiting for room broadcast")
		}
	}
	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.

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) 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) 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) ServeHTTP

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

ServeHTTP implements http.Handler, delegating to the Engine.IO server.

func (*Server) SetLogger

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

SetLogger sets the server logger.

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 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.

Jump to

Keyboard shortcuts

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