OddSockets Go SDK
Official Go SDK for OddSockets real-time messaging platform
Overview & Features
The OddSockets Go SDK provides a powerful, idiomatic Go interface for real-time messaging with full goroutine safety and excellent performance characteristics.
Idiomatic Go
Follows Go conventions with proper error handling, context support, and clean APIs.
Goroutine Safe
Thread-safe operations with proper synchronization for concurrent usage.
Type Safety
Strong typing with comprehensive struct definitions and interface contracts.
High Performance
Optimized for low latency with efficient WebSocket connections and minimal allocations.
Cost Effective
No per-message pricing, industry-standard 32KB message limits, transparent pricing.
Context Support
Full context.Context support for cancellation, timeouts, and request tracing.
Installation
go get github.com/oddsocketsai/go-sdk
                    module your-app
go 1.19
require (
    github.com/oddsocketsai/go-sdk v1.0.0
)
                    git clone https://github.com/oddsocketsai/go-sdk.git
cd go-sdk
go mod tidy
                    Quick Start
Basic Usage
package main
import (
    "context"
    "fmt"
    "log"
    
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
func main() {
    // Create client
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    // Connect to platform
    ctx := context.Background()
    if err := client.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    // Get channel
    channel := client.Channel("my-channel")
    // Subscribe to messages
    err = channel.Subscribe(ctx, func(msg *oddsockets.Message) {
        fmt.Printf("Received: %+v\n", msg)
    })
    if err != nil {
        log.Fatal(err)
    }
    // Publish a message
    err = channel.Publish(ctx, "Hello, World!")
    if err != nil {
        log.Fatal(err)
    }
}
                With Error Handling
package main
import (
    "context"
    "fmt"
    "log"
    "time"
    
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
func main() {
    // Create client with options
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey:            "ak_live_1234567890abcdef",
        UserID:            "user123",
        AutoConnect:       true,
        ReconnectAttempts: 5,
        HeartbeatInterval: 30 * time.Second,
    })
    if err != nil {
        log.Fatal("Failed to create client:", err)
    }
    defer client.Close()
    // Set up event handlers
    client.OnConnecting(func() {
        fmt.Println("🔄 Connecting...")
    })
    
    client.OnConnected(func() {
        fmt.Println("✅ Connected!")
    })
    
    client.OnDisconnected(func(reason string) {
        fmt.Printf("❌ Disconnected: %s\n", reason)
    })
    
    client.OnError(func(err error) {
        fmt.Printf("❌ Error: %v\n", err)
    })
    // Connect with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := client.Connect(ctx); err != nil {
        log.Fatal("Failed to connect:", err)
    }
    // Get channel and subscribe
    channel := client.Channel("my-channel")
    
    err = channel.Subscribe(ctx, func(msg *oddsockets.Message) {
        fmt.Printf("📨 Message: %s from %s\n", msg.Data, msg.UserID)
    }, &oddsockets.SubscribeOptions{
        EnablePresence: true,
        RetainHistory:  true,
        MaxHistory:     50,
    })
    if err != nil {
        log.Fatal("Failed to subscribe:", err)
    }
    // Publish with metadata
    err = channel.Publish(ctx, map[string]interface{}{
        "text": "Hello from Go SDK!",
        "user": "gopher",
    }, &oddsockets.PublishOptions{
        TTL:      3600,
        Metadata: map[string]interface{}{"priority": "high"},
    })
    if err != nil {
        log.Fatal("Failed to publish:", err)
    }
    // Keep the program running
    select {}
}
                Concurrent Usage
package main
import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"
    
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
func main() {
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    ctx := context.Background()
    if err := client.Connect(ctx); err != nil {
        log.Fatal(err)
    }
    var wg sync.WaitGroup
    
    // Start multiple goroutines for concurrent operations
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            
            channelName := fmt.Sprintf("channel-%d", id)
            channel := client.Channel(channelName)
            
            // Subscribe
            err := channel.Subscribe(ctx, func(msg *oddsockets.Message) {
                fmt.Printf("Channel %s received: %+v\n", channelName, msg)
            })
            if err != nil {
                log.Printf("Failed to subscribe to %s: %v", channelName, err)
                return
            }
            
            // Publish messages
            for j := 0; j < 5; j++ {
                message := fmt.Sprintf("Message %d from goroutine %d", j, id)
                if err := channel.Publish(ctx, message); err != nil {
                    log.Printf("Failed to publish to %s: %v", channelName, err)
                }
                time.Sleep(100 * time.Millisecond)
            }
        }(i)
    }
    
    wg.Wait()
}
                Configuration
Client Configuration
client, err := oddsockets.NewClient(&oddsockets.Config{
    APIKey:            "your-api-key",           // Required: Your OddSockets API key
    UserID:            "user-id",                // Optional: User identifier
    AutoConnect:       true,                     // Optional: Auto-connect on creation
    ReconnectAttempts: 5,                        // Optional: Max reconnection attempts
    HeartbeatInterval: 30 * time.Second,         // Optional: Heartbeat interval
    ConnectTimeout:    15 * time.Second,         // Optional: Connection timeout
    Logger:            log.New(os.Stdout, "", 0), // Optional: Custom logger
})
                Channel Options
// Subscribe with options
err = channel.Subscribe(ctx, messageHandler, &oddsockets.SubscribeOptions{
    EnablePresence: true,                        // Enable presence tracking
    RetainHistory:  true,                        // Retain message history
    MaxHistory:     100,                         // Maximum history size
    Filter:         "user.premium == true",      // Message filter expression
})
// Publish with options
err = channel.Publish(ctx, message, &oddsockets.PublishOptions{
    TTL:           3600,                         // Time to live (seconds)
    Metadata:      map[string]interface{}{       // Additional metadata
        "priority": "high",
        "source":   "go-sdk",
    },
    StoreInHistory: true,                        // Store in message history
})
                Context Usage
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// With deadline
deadline := time.Now().Add(5 * time.Minute)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
// With values for tracing
ctx = context.WithValue(ctx, "requestID", "req-123")
ctx = context.WithValue(ctx, "userID", "user-456")
                Examples
Explore comprehensive examples demonstrating the OddSockets Go SDK in action:
Performance & Compatibility
OddSockets Go SDK delivers excellent performance with broad Go version compatibility:
Go Version Support
- Go 1.19+ (recommended)
 - Go 1.18+ (supported)
 - Go Modules required
 - CGO not required
 
Platform Support
- Linux (amd64, arm64)
 - macOS (amd64, arm64)
 - Windows (amd64)
 - Docker containers
 
Framework Integrations
The OddSockets Go SDK works seamlessly with popular Go frameworks and libraries. Here are examples showing integration patterns:
Gin Web Framework
package main
import (
    "context"
    "net/http"
    
    "github.com/gin-gonic/gin"
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
type ChatService struct {
    client *oddsockets.Client
}
func NewChatService() (*ChatService, error) {
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        return nil, err
    }
    
    if err := client.Connect(context.Background()); err != nil {
        return nil, err
    }
    
    return &ChatService{client: client}, nil
}
func (cs *ChatService) SendMessage(c *gin.Context) {
    var req struct {
        Channel string      `json:"channel" binding:"required"`
        Message interface{} `json:"message" binding:"required"`
    }
    
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    channel := cs.client.Channel(req.Channel)
    if err := channel.Publish(c.Request.Context(), req.Message); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    
    c.JSON(http.StatusOK, gin.H{"status": "sent"})
}
func main() {
    chatService, err := NewChatService()
    if err != nil {
        panic(err)
    }
    
    r := gin.Default()
    r.POST("/send", chatService.SendMessage)
    r.Run(":8080")
}
                Echo Framework
package main
import (
    "context"
    "net/http"
    
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
type Server struct {
    echo   *echo.Echo
    client *oddsockets.Client
}
func NewServer() (*Server, error) {
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        return nil, err
    }
    
    if err := client.Connect(context.Background()); err != nil {
        return nil, err
    }
    
    e := echo.New()
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    
    server := &Server{
        echo:   e,
        client: client,
    }
    
    server.setupRoutes()
    return server, nil
}
func (s *Server) setupRoutes() {
    s.echo.POST("/channels/:channel/messages", s.publishMessage)
    s.echo.GET("/channels/:channel/history", s.getHistory)
    s.echo.GET("/channels/:channel/presence", s.getPresence)
}
func (s *Server) publishMessage(c echo.Context) error {
    channel := c.Param("channel")
    
    var message interface{}
    if err := c.Bind(&message); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    
    ch := s.client.Channel(channel)
    if err := ch.Publish(c.Request().Context(), message); err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
    }
    
    return c.JSON(http.StatusOK, map[string]string{"status": "published"})
}
func (s *Server) getHistory(c echo.Context) error {
    channel := c.Param("channel")
    
    ch := s.client.Channel(channel)
    history, err := ch.GetHistory(c.Request().Context(), &oddsockets.HistoryOptions{
        Count: 50,
    })
    if err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
    }
    
    return c.JSON(http.StatusOK, history)
}
func (s *Server) getPresence(c echo.Context) error {
    channel := c.Param("channel")
    
    ch := s.client.Channel(channel)
    presence, err := ch.GetPresence(c.Request().Context())
    if err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
    }
    
    return c.JSON(http.StatusOK, presence)
}
func main() {
    server, err := NewServer()
    if err != nil {
        panic(err)
    }
    
    server.echo.Logger.Fatal(server.echo.Start(":8080"))
}
                Fiber Framework
package main
import (
    "context"
    "log"
    
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/logger"
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
func main() {
    // Initialize OddSockets client
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()
    
    if err := client.Connect(context.Background()); err != nil {
        log.Fatal(err)
    }
    
    // Initialize Fiber app
    app := fiber.New(fiber.Config{
        ErrorHandler: func(c *fiber.Ctx, err error) error {
            return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
                "error": err.Error(),
            })
        },
    })
    
    // Middleware
    app.Use(logger.New())
    app.Use(cors.New())
    
    // Routes
    app.Post("/channels/:channel/publish", func(c *fiber.Ctx) error {
        channel := c.Params("channel")
        
        var body map[string]interface{}
        if err := c.BodyParser(&body); err != nil {
            return err
        }
        
        ch := client.Channel(channel)
        if err := ch.Publish(context.Background(), body); err != nil {
            return err
        }
        
        return c.JSON(fiber.Map{"status": "published"})
    })
    
    app.Get("/channels/:channel/subscribe", func(c *fiber.Ctx) error {
        channel := c.Params("channel")
        
        ch := client.Channel(channel)
        
        // Set up SSE headers
        c.Set("Content-Type", "text/event-stream")
        c.Set("Cache-Control", "no-cache")
        c.Set("Connection", "keep-alive")
        
        // Subscribe and stream messages
        err := ch.Subscribe(context.Background(), func(msg *oddsockets.Message) {
            c.WriteString("data: " + string(msg.Data) + "\n\n")
        })
        
        return err
    })
    
    log.Fatal(app.Listen(":8080"))
}
                gRPC Integration
package main
import (
    "context"
    "log"
    "net"
    
    "google.golang.org/grpc"
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
type ChatServer struct {
    client *oddsockets.Client
    // UnimplementedChatServiceServer
}
func NewChatServer() (*ChatServer, error) {
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        return nil, err
    }
    
    if err := client.Connect(context.Background()); err != nil {
        return nil, err
    }
    
    return &ChatServer{client: client}, nil
}
func (s *ChatServer) SendMessage(ctx context.Context, req *SendMessageRequest) (*SendMessageResponse, error) {
    channel := s.client.Channel(req.Channel)
    
    err := channel.Publish(ctx, map[string]interface{}{
        "text":   req.Message,
        "userId": req.UserId,
    })
    if err != nil {
        return nil, err
    }
    
    return &SendMessageResponse{Success: true}, nil
}
func (s *ChatServer) Subscribe(req *SubscribeRequest, stream ChatService_SubscribeServer) error {
    channel := s.client.Channel(req.Channel)
    
    return channel.Subscribe(stream.Context(), func(msg *oddsockets.Message) {
        response := &MessageEvent{
            Channel:   req.Channel,
            Message:   string(msg.Data),
            UserId:    msg.UserID,
            Timestamp: msg.Timestamp.Unix(),
        }
        
        if err := stream.Send(response); err != nil {
            log.Printf("Failed to send message: %v", err)
        }
    })
}
func main() {
    server, err := NewChatServer()
    if err != nil {
        log.Fatal(err)
    }
    
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatal(err)
    }
    
    s := grpc.NewServer()
    RegisterChatServiceServer(s, server)
    
    log.Println("gRPC server listening on :50051")
    log.Fatal(s.Serve(lis))
}
                Worker Pool Pattern
package main
import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"
    
    "github.com/oddsocketsai/go-sdk/oddsockets"
)
type MessageProcessor struct {
    client   *oddsockets.Client
    workers  int
    jobQueue chan *oddsockets.Message
    wg       sync.WaitGroup
}
func NewMessageProcessor(workers int) (*MessageProcessor, error) {
    client, err := oddsockets.NewClient(&oddsockets.Config{
        APIKey: "ak_live_1234567890abcdef",
    })
    if err != nil {
        return nil, err
    }
    
    if err := client.Connect(context.Background()); err != nil {
        return nil, err
    }
    
    return &MessageProcessor{
        client:   client,
        workers:  workers,
        jobQueue: make(chan *oddsockets.Message, 100),
    }, nil
}
func (mp *MessageProcessor) Start(ctx context.Context) {
    // Start worker goroutines
    for i := 0; i < mp.workers; i++ {
        mp.wg.Add(1)
        go mp.worker(ctx, i)
    }
    
    // Subscribe to incoming messages
    channel := mp.client.Channel("work-queue")
    err := channel.Subscribe(ctx, func(msg *oddsockets.Message) {
        select {
        case mp.jobQueue <- msg:
        case <-ctx.Done():
            return
        default:
            log.Println("Job queue full, dropping message")
        }
    })
    if err != nil {
        log.Fatal("Failed to subscribe:", err)
    }
}
func (mp *MessageProcessor) worker(ctx context.Context, id int) {
    defer mp.wg.Done()
    
    for {
        select {
        case msg := <-mp.jobQueue:
            mp.processMessage(ctx, id, msg)
        case <-ctx.Done():
            return
        }
    }
}
func (mp *MessageProcessor) processMessage(ctx context.Context, workerID int, msg *oddsockets.Message) {
    fmt.Printf("Worker %d processing message: %s\n", workerID, msg.Data)
    
    // Simulate work
    time.Sleep(100 * time.Millisecond)
    
    // Send result to results channel
    resultChannel := mp.client.Channel("results")
    result := map[string]interface{}{
        "originalMessage": string(msg.Data),
        "processedBy":     workerID,
        "processedAt":     time.Now().Unix(),
    }
    
    if err := resultChannel.Publish(ctx, result); err != nil {
        log.Printf("Worker %d failed to publish result: %v", workerID, err)
    }
}
func (mp *MessageProcessor) Stop() {
    close(mp.jobQueue)
    mp.wg.Wait()
    mp.client.Close()
}
func main() {
    processor, err := NewMessageProcessor(5)
    if err != nil {
        log.Fatal(err)
    }
    defer processor.Stop()
    
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    
    processor.Start(ctx)
    
    // Keep running
    select {}
}