Documentation
¶
Overview ¶
Package client provides a robust WebSocket client for webhook sprinkler servers.
The client handles:
- Automatic reconnection with exponential backoff
- Ping/pong keep-alive messages
- Structured logging with customizable output
- Event callbacks for custom processing
- Graceful shutdown
Basic usage:
config := client.Config{
ServerURL: "wss://example.com/ws",
Organization: "myorg",
Token: "ghp_...",
OnEvent: func(event client.Event) {
fmt.Printf("Got event: %s\n", event.Type)
},
}
c, err := client.New(config)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
if err := c.Start(ctx); err != nil {
log.Fatal(err)
}
To disable logging or customize output:
import "log/slog" import "io" // Silence all logs config.Logger = slog.New(slog.NewTextHandler(io.Discard, nil)) // Or use JSON logging config.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
Index ¶
Examples ¶
Constants ¶
View Source
const ( // DefaultServerAddress is the default webhook sprinkler server address. DefaultServerAddress = "webhook.github.codegroove.app" // Version is the client library version. Version = "v0.5.0" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuthenticationError ¶
type AuthenticationError struct {
// contains filtered or unexported fields
}
AuthenticationError represents an authentication or authorization failure that should not trigger reconnection attempts.
func (*AuthenticationError) Error ¶
func (e *AuthenticationError) Error() string
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client represents a WebSocket client with automatic reconnection. Connection management:
- Read loop (readEvents) receives all messages from server
- Write channel (writeCh) serializes all writes through one goroutine
- Server sends pings; client responds with pongs
- Client also sends pings; server responds with pongs
- Both sides use read timeouts to detect dead connections
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/codeGROOVE-dev/sprinkler/pkg/client"
)
func main() {
// Create client configuration
config := client.Config{
ServerURL: "wss://hook.example.com/ws",
UserAgent: "myapp/v1.0.0", // Required: client name and version
Organization: "myorg",
Token: "ghp_yourtoken",
EventTypes: []string{"pull_request", "issue_comment"},
UserEventsOnly: true,
Verbose: false,
MaxRetries: 5,
OnEvent: func(event client.Event) {
// Process each event
fmt.Printf("Event: %s at %s\n", event.Type, event.URL)
},
OnConnect: func() {
log.Println("Connected successfully!")
},
OnDisconnect: func(err error) {
log.Printf("Disconnected: %v", err)
},
}
// Create the client
c, err := client.New(config)
if err != nil {
log.Print(err)
return
}
// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Start the client (blocks until error or context cancellation)
if err := c.Start(ctx); err != nil {
log.Printf("Client stopped: %v", err)
}
}
Example (CustomLogger) ¶
package main
import (
"log"
"log/slog"
"os"
"github.com/codeGROOVE-dev/sprinkler/pkg/client"
)
func main() {
// Example 1: Silence all logs
silentLogger := slog.New(slog.DiscardHandler)
// Example 2: JSON logging to a file
logFile, err := os.Create("client.log")
if err == nil {
defer func() {
if err := logFile.Close(); err != nil {
log.Printf("Failed to close log file: %v", err)
}
}()
}
var jsonLogger *slog.Logger
if logFile != nil {
jsonLogger = slog.New(slog.NewJSONHandler(logFile, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
}
// Example 3: Structured logging with custom format
structuredLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
// Use the silent logger for a client that produces no output
config := client.Config{
ServerURL: "wss://hook.example.com/ws",
Organization: "myorg",
Token: "ghp_yourtoken",
Logger: silentLogger, // No log output
}
c, err := client.New(config)
if err != nil {
log.Print(err)
return
}
// Alternative: use the JSON logger
if jsonLogger != nil {
config.Logger = jsonLogger
c2, err := client.New(config)
if err != nil {
log.Print(err)
return
}
_ = c2
}
// Alternative: use structured text logger
config.Logger = structuredLogger
c3, err := client.New(config)
if err != nil {
log.Print(err)
return
}
_ = c
_ = c3
}
Example (GracefulShutdown) ¶
package main
import (
"context"
"log"
"time"
"github.com/codeGROOVE-dev/sprinkler/pkg/client"
)
func main() {
config := client.Config{
ServerURL: "wss://hook.example.com/ws",
UserAgent: "myapp/v1.0.0",
Organization: "myorg",
Token: "ghp_yourtoken",
}
c, err := client.New(config)
if err != nil {
log.Print(err)
return
}
// Start client in goroutine
ctx := context.Background()
go func() {
if err := c.Start(ctx); err != nil {
log.Printf("Client error: %v", err)
}
}()
// Do some work...
time.Sleep(10 * time.Second)
// Gracefully stop the client
c.Stop()
}
type Config ¶
type Config struct {
Logger *slog.Logger
OnDisconnect func(error)
OnEvent func(Event)
OnConnect func()
ServerURL string
Token string
TokenProvider func() (string, error) // Optional: dynamically provide fresh tokens for reconnection
UserAgent string // Required: User-Agent in format "client-name/version" (e.g., "myapp/v1.0.0")
Organization string
EventTypes []string
PullRequests []string
MaxBackoff time.Duration
PingInterval time.Duration
MaxRetries int
UserEventsOnly bool
Verbose bool
NoReconnect bool
}
Config holds the configuration for the client.
type Event ¶
type Event struct {
Timestamp time.Time `json:"timestamp"`
Raw map[string]any
Type string `json:"type"`
URL string `json:"url"` // PR URL (or repo URL for check events with race condition)
DeliveryID string `json:"delivery_id,omitempty"`
CommitSHA string `json:"commit_sha,omitempty"` // Commit SHA for check events
}
Event represents a webhook event received from the server.
Click to show internal directories.
Click to hide internal directories.