client

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 10 Imported by: 0

README

client — Go Socket.IO client

Go Reference Parity

Package client is a Go Socket.IO client. It connects to a Socket.IO server (this module's server or the Node reference server) over the WebSocket transport and provides the familiar On/Emit/EmitWithAck API that mirrors the browser socket.io-client, letting a Go process act as a first-class Socket.IO peer rather than merely a server:

c, err := client.Dial("http://localhost:3000")
c.On("news", func(args []any) []any { fmt.Println(args); return nil })
c.Emit("hello", "world")

Use it whenever a Go program needs to talk to a Socket.IO endpoint as a client: integration and end-to-end tests against a running server, service-to- service messaging where the other side already speaks Socket.IO, bots and load generators, or bridging a Socket.IO event stream into another system. Dial blocks until the Socket.IO CONNECT handshake for the chosen namespace completes (or DialTimeout elapses), so a successful return means the client is fully connected and ready to Emit. Optional authentication data is sent with the CONNECT packet via Options.Auth, matching the server-side socket.Auth().

Install

go get github.com/malcolmston/socketio@v0.5.0
import "github.com/malcolmston/socketio/client"

Usage

This is the package's own ExampleDial, so it compiles and its output is asserted on every go test ./client/.

c, err := client.Dial("http://localhost:3000", client.Options{
		Namespace:    "/",
		Auth:         map[string]any{"token": "s3cret"},
		Reconnection: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	c.On("news", func(args []any) []any {
		fmt.Println("news:", args)
		return nil
	})

	if err := c.Emit("hello", "world"); err != nil {
		log.Fatal(err)
	}

	reply, err := c.EmitWithAck("question", 5*time.Second, "what is 2+2?")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("answer:", reply)

Exported surface

Functions
Function What it does
func ConnectPacket(namespace string, auth any) socketio.Packet ConnectPacket builds the Socket.IO CONNECT packet that opens a namespace, carrying auth as its payload when non-nil.
func EventPacket(namespace, event string, ackID *uint64, args []any) (socketio.Packet, error) EventPacket builds the EVENT packet an Emit sends: the event name followed by its arguments, addressed to namespace.
Types
Type What it is
AckIDs AckIDs allocates the acknowledgement ids an emit claims: 0 for the first acknowledged emit on a connection, then 1, 2, … .
Backoff Backoff computes an exponentially increasing delay with optional jitter, mirroring the JavaScript client's backo2 module used for reconnection.
BackoffOptions BackoffOptions configures a Backoff.
Client Client is a connected Socket.IO client.
Endpoint Endpoint is where a Dial will actually connect: the Engine.IO WebSocket URL and the Socket.IO namespace to hand shake for.
Handler Handler handles an inbound event.
Options Options configures Dial.
AckIDs — constructors and methods
Signature What it does
func (a *AckIDs) Next() uint64 Next returns the id for the next acknowledged emit and advances the counter.
Backoff — constructors and methods
Signature What it does
func NewBackoff(opts BackoffOptions) *Backoff NewBackoff creates a Backoff from the supplied options, filling in defaults for any zero field.
func (b *Backoff) Attempts() int Attempts returns how many times Duration has been called since the last Reset.
func (b *Backoff) Duration() time.Duration Duration returns the delay for the current attempt and advances to the next, computing exactly what backo2 (the module socket.io-client's Manager…
func (b *Backoff) Reset() Reset returns the backoff to its initial state, so the next Duration call yields the minimum delay again.
Client — constructors and methods
Signature What it does
func Dial(rawURL string, opts ...Options) (*Client, error) Dial connects to a Socket.IO server at rawURL (http:// or ws://) and returns once the Socket.IO CONNECT handshake completes.
func (c *Client) Close() error Close disconnects from the server.
func (c *Client) Emit(event string, args ...any) error Emit sends an event to the server.
func (c *Client) EmitWithAck(event string, timeout time.Duration, args ...any) ([]any, error) EmitWithAck sends an event and waits for the server's acknowledgement, up to timeout.
func (c *Client) ID() string ID returns the socket id assigned by the server.
func (c *Client) On(event string, h Handler) On registers a handler for an event.
Endpoint — constructors and methods
Signature What it does
func ResolveEndpoint(rawURL string, opts Options) (Endpoint, error) ResolveEndpoint derives the Engine.IO WebSocket URL and the Socket.IO namespace from a user-supplied URL, following the same rules as…
Options — constructors and methods
Signature What it does
func (o Options) WithDefaults() Options WithDefaults returns a copy of o with every unset field filled in with the default documented on it.
Constants

DefaultPath

Full signatures, doc comments and every runnable example are on pkg.go.dev.

Measured parity

Compared case-for-case against socket.io-client@4.8.1; socket.io-client@4.8.1 + socket.io-parser@4.2.7 by the harness in parity/socket.io/nested/client:

Parity 100%
Cases 101
Matching 95
Mismatching 0
Declared deviations 6

Regenerate from the aggregator repo with go test ./parity/socket.io/nested/client/. Declared deviations are documented differences, excluded from the denominator and listed in the harness report.

Deviations from upstream

Deliberate differences for this package, where any exist, are recorded in the module-wide API-DEVIATIONS.md.

License

MIT, as part of github.com/malcolmston/socketio. An independent re-implementation, not affiliated with or endorsed by the original project.

Documentation

Overview

Package client is a Go Socket.IO client. It connects to a Socket.IO server (this module's server or the Node reference server) over the WebSocket transport and provides the familiar On/Emit/EmitWithAck API that mirrors the browser socket.io-client, letting a Go process act as a first-class Socket.IO peer rather than merely a server:

c, err := client.Dial("http://localhost:3000")
c.On("news", func(args []any) []any { fmt.Println(args); return nil })
c.Emit("hello", "world")

Use it whenever a Go program needs to talk to a Socket.IO endpoint as a client: integration and end-to-end tests against a running server, service-to- service messaging where the other side already speaks Socket.IO, bots and load generators, or bridging a Socket.IO event stream into another system. Dial blocks until the Socket.IO CONNECT handshake for the chosen namespace completes (or DialTimeout elapses), so a successful return means the client is fully connected and ready to Emit. Optional authentication data is sent with the CONNECT packet via Options.Auth, matching the server-side socket.Auth().

Addressing follows socket.io-client: the PATH of the dialled URL names the namespace, so Dial("http://host/admin") connects to namespace "/admin", while the HTTP path the server is mounted on is Options.Path (default DefaultPath, "/socket.io/"). See ResolveEndpoint for the full set of rules.

Under the hood the client speaks Engine.IO v4 directly over a single WebSocket (it does not use HTTP long-polling): it dials ws(s)://.../socket.io/ with EIO=4&transport=websocket, reads the Engine.IO OPEN handshake, sends the Socket.IO CONNECT packet, and then runs a background read loop that answers heartbeat pings, dispatches inbound EVENT packets to handlers registered with On, and resolves acknowledgements. Outgoing events are encoded with the shared socketio packet codec, so binary ([]byte) arguments are automatically split into BINARY_EVENT attachment frames and reassembled on receipt.

Concurrency and lifecycle: On, Emit, EmitWithAck, and Close are safe to call from multiple goroutines. Handlers run on the read-loop goroutine, so a handler that itself blocks on EmitWithAck should hand off to another goroutine to avoid deadlocking the loop; returning a non-nil slice from a handler acknowledges an event the server sent with an ack id. EmitWithAck waits up to the supplied timeout and returns an error if no acknowledgement arrives. When Options.Reconnection is enabled, an unexpected transport drop triggers a reconnect loop on the same schedule as the JavaScript client's Manager — a Backoff seeded with ReconnectionDelay, ReconnectionDelayMax and RandomizationFactor, bounded by ReconnectionAttempts — and fires the local "reconnect" event; a user-initiated Close suppresses reconnection. Note that socket.io-client reconnects by default whereas Options.Reconnection must be set explicitly, because a Go zero value cannot express a true default.

Compared with the JavaScript socket.io-client the surface is intentionally small: one namespace per Client, no long-polling fallback, and lifecycle events surfaced as ordinary events ("disconnect", "reconnect") registered through On. The wire protocol is identical, so this client interoperates with the Node reference server and with this module's server package. For multi-namespace usage, Dial a separate Client per namespace.

Index

Examples

Constants

View Source
const DefaultPath = "/socket.io/"

DefaultPath is the HTTP path a Socket.IO server is mounted on, and the path the client requests unless Options.Path says otherwise. It is the counterpart of socket.io-client's `path` option (whose default is "/socket.io").

Variables

This section is empty.

Functions

func ConnectPacket added in v0.5.0

func ConnectPacket(namespace string, auth any) socketio.Packet

ConnectPacket builds the Socket.IO CONNECT packet that opens a namespace, carrying auth as its payload when non-nil. It is what Dial sends after the Engine.IO handshake, and the counterpart of socket.io-client's Socket._sendConnectPacket.

func EventPacket added in v0.5.0

func EventPacket(namespace, event string, ackID *uint64, args []any) (socketio.Packet, error)

EventPacket builds the EVENT packet an Emit sends: the event name followed by its arguments, addressed to namespace. A non-nil ackID asks the server for an acknowledgement under that id (what EmitWithAck does). Reserved Socket.IO event names are refused with socketio.ErrReservedEvent, matching socket.io-client, which throws for them.

Types

type AckIDs added in v0.5.0

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

AckIDs allocates the acknowledgement ids an emit claims: 0 for the first acknowledged emit on a connection, then 1, 2, … . It is the counterpart of socket.io-client's Socket.ids counter, and every Client owns one. The zero value is ready to use and is safe for concurrent use.

func (*AckIDs) Next added in v0.5.0

func (a *AckIDs) Next() uint64

Next returns the id for the next acknowledged emit and advances the counter.

type Backoff added in v0.3.0

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

Backoff computes an exponentially increasing delay with optional jitter, mirroring the JavaScript client's backo2 module used for reconnection. Each call to Duration returns the delay for the current attempt and advances the attempt counter; Reset returns to the initial delay. A Backoff is safe for concurrent use.

func NewBackoff added in v0.3.0

func NewBackoff(opts BackoffOptions) *Backoff

NewBackoff creates a Backoff from the supplied options, filling in defaults for any zero field.

func (*Backoff) Attempts added in v0.3.0

func (b *Backoff) Attempts() int

Attempts returns how many times Duration has been called since the last Reset.

func (*Backoff) Duration added in v0.3.0

func (b *Backoff) Duration() time.Duration

Duration returns the delay for the current attempt and advances to the next, computing exactly what backo2 (the module socket.io-client's Manager uses) would compute:

ms        = min * factor^attempt          // NOT clamped yet
deviation = floor(rand * jitter * ms)     // jitter only, when a Rand is set
ms        = ms ± deviation                // sign from the same draw
result    = trunc(min(ms, max)) in whole milliseconds

The order matters: the ceiling is applied after the jitter, not before, and the deviation is floored to whole milliseconds — so a jittered delay derived from an already-clamped base (or from an unfloored deviation) would drift away from the JavaScript client's schedule.

func (*Backoff) Reset added in v0.3.0

func (b *Backoff) Reset()

Reset returns the backoff to its initial state, so the next Duration call yields the minimum delay again.

type BackoffOptions added in v0.3.0

type BackoffOptions struct {
	// Min is the initial delay (default 100ms).
	Min time.Duration
	// Max is the ceiling the delay is clamped to (default 10s).
	Max time.Duration
	// Factor is the exponential growth base per attempt (default 2).
	Factor float64
	// Jitter is the randomization fraction in (0,1]; 0 — or any value outside
	// that range, as in backo2 — disables jitter. A value of 0.5 spreads each
	// delay by up to ±50%. Jitter is only applied when Rand is non-nil, keeping
	// a default Backoff fully deterministic.
	Jitter float64
	// Rand supplies the randomness used for jitter; it must return a value in
	// [0,1). Inject a deterministic function in tests, or math/rand's Float64 in
	// production. When nil, no jitter is applied.
	Rand func() float64
}

BackoffOptions configures a Backoff. The zero value yields sensible defaults (100ms initial, 10s ceiling, factor 2, no jitter).

type Client

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

Client is a connected Socket.IO client.

func Dial

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

Dial connects to a Socket.IO server at rawURL (http:// or ws://) and returns once the Socket.IO CONNECT handshake completes. The path of rawURL names the namespace, as in socket.io-client — see ResolveEndpoint.

Example

ExampleDial demonstrates the full lifecycle of a Go Socket.IO client. It calls Dial to connect to a running server, passing Options to select a namespace and to enable automatic reconnection; Dial blocks until the CONNECT handshake completes, so a nil error means the client is ready to use. The example then registers a handler for the server-pushed "news" event with On, sends a fire-and-forget event with Emit, and issues a request/response round-trip with EmitWithAck that waits up to five seconds for the server's acknowledgement. Finally it closes the connection with a deferred Close, which also suppresses any pending reconnection. The reader should take away how a Go program becomes a first-class Socket.IO peer using the same On/Emit/EmitWithAck vocabulary as the browser client. (This example is compiled to verify the API but is not run here, since it needs a live server to connect to.)

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/malcolmston/socketio/client"
)

func main() {
	c, err := client.Dial("http://localhost:3000", client.Options{
		Namespace:    "/",
		Auth:         map[string]any{"token": "s3cret"},
		Reconnection: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	// Handle events the server pushes to us.
	c.On("news", func(args []any) []any {
		fmt.Println("news:", args)
		return nil
	})

	// Fire-and-forget emit.
	if err := c.Emit("hello", "world"); err != nil {
		log.Fatal(err)
	}

	// Request/response with an acknowledgement.
	reply, err := c.EmitWithAck("question", 5*time.Second, "what is 2+2?")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("answer:", reply)
}

func (*Client) Close

func (c *Client) Close() error

Close disconnects from the server.

func (*Client) Emit

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

Emit sends an event to the server. Emitting a reserved Socket.IO event name (connect, connect_error, disconnect, disconnecting, ...) returns socketio.ErrReservedEvent without sending anything, matching socket.io-client, which throws for those names.

func (*Client) EmitWithAck

func (c *Client) EmitWithAck(event string, timeout time.Duration, args ...any) ([]any, error)

EmitWithAck sends an event and waits for the server's acknowledgement, up to timeout.

func (*Client) ID

func (c *Client) ID() string

ID returns the socket id assigned by the server.

func (*Client) On

func (c *Client) On(event string, h Handler)

On registers a handler for an event.

type Endpoint added in v0.5.0

type Endpoint struct {
	// URL is the ws:// or wss:// URL of the Engine.IO endpoint, including the
	// mandatory EIO=4&transport=websocket query.
	URL string
	// Namespace is the Socket.IO namespace the CONNECT packet will name.
	Namespace string
}

Endpoint is where a Dial will actually connect: the Engine.IO WebSocket URL and the Socket.IO namespace to hand shake for. It is the result of applying socket.io-client's addressing rules to a user-supplied URL.

func ResolveEndpoint added in v0.5.0

func ResolveEndpoint(rawURL string, opts Options) (Endpoint, error)

ResolveEndpoint derives the Engine.IO WebSocket URL and the Socket.IO namespace from a user-supplied URL, following the same rules as socket.io-client's url() plus Transport.uri():

  • http/ws become ws, https/wss become wss;
  • the URL's PATH is the namespace, not the HTTP path — io("http://h/admin") connects to namespace "/admin". The HTTP path the server is mounted on is Options.Path (default DefaultPath), never taken from the URL;
  • a port that is the default for the scheme (80 for ws, 443 for wss) is omitted, so ws://h:80/ and ws://h/ produce one endpoint, not two;
  • userinfo is dropped: Engine.IO addresses the host, and credentials belong in Options.Auth or a header, not in the transport URL;
  • any query in the URL is preserved, with EIO=4 and transport=websocket added.

An explicit Options.Namespace wins over the namespace in the URL. Dial calls this; it is exported so that addressing can be inspected (and tested) without opening a connection.

type Handler

type Handler func(args []any) []any

Handler handles an inbound event. Returning a non-nil slice acknowledges an event the server sent with an ack id.

type Options

type Options struct {
	// Namespace to connect to. When empty the namespace is taken from the path
	// of the dialled URL (socket.io-client's rule), which is "/" for a bare
	// host.
	Namespace string
	// Path is the HTTP path the server is mounted on (default DefaultPath). It
	// is never taken from the dialled URL, whose path names the namespace. A
	// missing trailing slash is added.
	Path string
	// Auth is an optional payload sent with the CONNECT packet.
	Auth any
	// DialTimeout bounds the connection handshake (default 20s, matching
	// socket.io-client's `timeout`).
	DialTimeout time.Duration
	// Reconnection enables automatic reconnection after an unexpected
	// disconnect. Note that socket.io-client reconnects by DEFAULT; a Go zero
	// value cannot express a true default, so this must be set explicitly (see
	// API-DEVIATIONS.md).
	Reconnection bool
	// ReconnectionAttempts bounds reconnection tries (0 = unlimited, which is
	// socket.io-client's Infinity default).
	ReconnectionAttempts int
	// ReconnectionDelay is the initial delay between attempts (default 1s).
	ReconnectionDelay time.Duration
	// ReconnectionDelayMax is the ceiling the delay grows to (default 5s).
	ReconnectionDelayMax time.Duration
	// RandomizationFactor spreads each reconnection delay by up to this
	// fraction, in [0,1]. socket.io-client defaults it to 0.5; a Go zero value
	// cannot express that default, so jitter is off unless set (see
	// API-DEVIATIONS.md).
	RandomizationFactor float64
}

Options configures Dial. The zero value is usable: every field below states the default applied by WithDefaults.

func (Options) WithDefaults added in v0.5.0

func (o Options) WithDefaults() Options

WithDefaults returns a copy of o with every unset field filled in with the default documented on it. Dial and ResolveEndpoint both apply it, so the options a Client runs with are always the normalised ones.

Jump to

Keyboard shortcuts

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