Skip to content

Event System

Socket.IO is based on a bidirectional event system. This document explains all available events and how to use them.

Table of Contents

Lifecycle Events

These events are automatically emitted during the connection lifecycle.

OnConnect

Executed when the connection is successfully established.

client.OnConnect(func() {
  fmt.Println("Connected to server")

  // You can emit initial events here
  client.Emit("join-room", "room-123")
})

OnDisconnect

Executed when the connection closes. Receives the disconnection reason.

client.OnDisconnect(func(reason string) {
  fmt.Printf("Disconnected: %s\n", reason)

  // Common reasons:
  // - "io server disconnect" - Server closed the connection
  // - "io client disconnect" - Client closed the connection
  // - "ping timeout" - Server did not respond to ping
  // - "transport close" - Underlying transport closed
  // - "transport error" - Transport error
})

OnError

Executed when a connection error occurs.

client.OnError(func(err error) {
  fmt.Printf("Connection error: %v\n", err)

  // You can implement recovery logic here
  if err.Error() == "authentication failed" {
    fmt.Println("Check your credentials")
  }
})

Reconnection Events

These events allow you to monitor and control the automatic reconnection process.

OnReconnectAttempt

Executed before each reconnection attempt.

client.OnReconnectAttempt(func(attempt int) {
  fmt.Printf("Attempting to reconnect... (attempt %d)\n", attempt)
})

OnReconnect

Executed when reconnection is successful.

client.OnReconnect(func(attempt int) {
  fmt.Printf("Successfully reconnected after %d attempts\n", attempt)

  // Re-subscribe to events or update state
  client.Emit("resume-session", lastSessionId)
})

OnReconnectError

Executed when a reconnection attempt fails.

client.OnReconnectError(func(err error) {
  fmt.Printf("Reconnection error: %v\n", err)
})

OnReconnectFailed

Executed when all reconnection attempts fail.

client.OnReconnectFailed(func() {
  fmt.Println("Reconnection failed after all attempts")

  // Notify user or implement fallback
  notifyUser("Could not re-establish connection")
})

Complete Reconnection Example

client := socketio.New("ws://localhost:3000", socketio.Options{
  ReconnectAttempts: 5,
  ReconnectDelay:    time.Second,
  ReconnectDelayMax: 5 * time.Second,
})

var reconnectCount int

client.OnReconnectAttempt(func(attempt int) {
  reconnectCount = attempt
  fmt.Printf("⏳ Reconnection attempt %d...\n", attempt)
})

client.OnReconnect(func(attempt int) {
  fmt.Printf("✅ Reconnected after %d attempts\n", attempt)
  reconnectCount = 0
})

client.OnReconnectError(func(err error) {
  fmt.Printf("❌ Error on attempt %d: %v\n", reconnectCount, err)
})

client.OnReconnectFailed(func() {
  fmt.Println("💥 Reconnection failed permanently")
})

Custom Events

You can listen to any custom event emitted by the server.

Listening to Events

client.On("message", func(data ...interface{}) {
  fmt.Printf("Message received: %v\n", data[0])
})

client.On("user-joined", func(data ...interface{}) {
  username := data[0].(string)
  fmt.Printf("%s joined the room\n", username)
})

client.On("notification", func(data ...interface{}) {
  notification := data[0].(map[string]interface{})
  fmt.Printf("Notification: %s\n", notification["message"])
})

Multiple Arguments

Events can receive multiple arguments:

client.On("chat-message", func(data ...interface{}) {
  username := data[0].(string)
  message := data[1].(string)
  timestamp := data[2].(float64)

  fmt.Printf("[%s] %s: %s\n",
    time.Unix(int64(timestamp), 0).Format("15:04"),
    username,
    message)
})

Namespaces

Events can be listened to on specific namespaces:

// Default namespace
client.On("global-event", func(data ...interface{}) {
  fmt.Println("Global event:", data[0])
})

// Custom namespace
chat := client.Of("/chat")
chat.On("message", func(data ...interface{}) {
  fmt.Println("Chat message:", data[0])
})

admin := client.Of("/admin")
admin.On("alert", func(data ...interface{}) {
  fmt.Println("Admin alert:", data[0])
})

Event Handlers

Handler Signature

Event handlers have the following signature:

type EventHandler func(data ...interface{})

The data parameter contains all arguments sent with the event.

Type Assertions

You must perform type assertions to use the received data:

client.On("user-data", func(data ...interface{}) {
  if len(data) == 0 {
    return
  }

  // Type assertion to map
  if userData, ok := data[0].(map[string]interface{}); ok {
    name := userData["name"].(string)
    age := userData["age"].(float64)
    fmt.Printf("User: %s, Age: %.0f\n", name, age)
  }
})

Error Handling in Handlers

client.On("data", func(data ...interface{}) {
  defer func() {
    if r := recover(); r != nil {
      fmt.Printf("Handler error: %v\n", r)
    }
  }()

  // Process data
  processData(data[0])
})

Acknowledgments

Acknowledgments enable bidirectional responses.

Emitting with Acknowledgment

client.EmitWithAck("get-user", func(response ...interface{}) {
  user := response[0].(map[string]interface{})
  fmt.Printf("User retrieved: %v\n", user)
}, "user-123")

Responding to Acknowledgments

client.On("save-data", func(data ...interface{}) {
  // Last argument is the acknowledgment callback
  if len(data) < 2 {
    return
  }

  payload := data[0]

  if ack, ok := data[len(data)-1].(func(...interface{})); ok {
    // Process data
    success := saveToDatabase(payload)

    // Respond to server
    if success {
      ack(map[string]interface{}{
        "status": "ok",
        "message": "Data saved successfully",
      })
    } else {
      ack(map[string]interface{}{
        "status": "error",
        "message": "Error saving data",
      })
    }
  }
})

Acknowledgment Timeout

You can configure a global timeout for acknowledgments:

client := socketio.New("ws://localhost:3000", socketio.Options{
  AckTimeout: 5 * time.Second, // 5 second timeout
})

client.EmitWithAck("slow-operation", func(response ...interface{}) {
  if response == nil {
    fmt.Println("Timeout: server did not respond in time")
    return
  }
  fmt.Println("Response:", response[0])
}, "data")

Binary Events

To send binary data (images, files, etc.):

// Read file
imageData, err := os.ReadFile("photo.jpg")
if err != nil {
  log.Fatal(err)
}

// Emit binary event
client.Of("/").EmitBinary("upload", imageData, map[string]interface{}{
  "filename": "photo.jpg",
  "size": len(imageData),
})

Receiving Binary Data

client.On("file", func(data ...interface{}) {
  // First argument is the binary buffer
  if binaryData, ok := data[0].([]byte); ok {
    fmt.Printf("File received: %d bytes\n", len(binaryData))

    // Save file
    err := os.WriteFile("downloaded.dat", binaryData, 0644)
    if err != nil {
      fmt.Println("Error saving file:", err)
    }
  }

  // Additional arguments
  if len(data) > 1 {
    metadata := data[1].(map[string]interface{})
    fmt.Printf("Metadata: %v\n", metadata)
  }
})

Best Practices

1. Validate Received Data

Always validate data before using it:

client.On("update", func(data ...interface{}) {
  if len(data) == 0 {
    fmt.Println("Event with no data")
    return
  }

  payload, ok := data[0].(map[string]interface{})
  if !ok {
    fmt.Println("Invalid data format")
    return
  }

  // Use validated data
  processUpdate(payload)
})

2. Use Goroutines for Long Operations

Don't block the event handler with long operations:

client.On("process", func(data ...interface{}) {
  go func() {
    // Long operation
    result := heavyComputation(data[0])

    // Emit result
    client.Emit("result", result)
  }()
})

3. Clean Up Resources

Unregister handlers when you no longer need them:

// Client doesn't provide Off() directly
// Instead, use flags or channels to control execution

done := make(chan struct{})

client.On("temp-event", func(data ...interface{}) {
  select {
  case <-done:
    return // Ignore event
  default:
    processEvent(data)
  }
})

// When finished
close(done)

4. Structured Logging

Implement consistent logging:

func logEvent(event string, data ...interface{}) {
  fmt.Printf("[%s] Event: %s, Data: %v\n",
    time.Now().Format("15:04:05"),
    event,
    data)
}

client.On("important-event", func(data ...interface{}) {
  logEvent("important-event", data...)
  processEvent(data)
})

See Also