// Package wsutil provides a small, reusable WebSocket gateway client for // stateful IM channels (KOOK, QQ OneBot, and similar). It owns the transport // mechanics that those channels share — dialing a gateway, running a read loop, // sending an application-level heartbeat, and reconnecting with exponential // backoff (optionally splicing resume parameters onto the gateway URL) — while // leaving the platform-specific signaling to the adapter via Handler hooks. // // It depends only on the standard library plus github.com/gorilla/websocket and // internal/logger, never on a specific channel adapter. package wsutil import "time" // Backoff is a resettable exponential backoff delay scheduler. It yields delays // starting at base and doubling up to cap (for example 2s, 4s, 8s, ... capped at // 60s), which is the WebSocket gateway reconnect pattern used by stateful IM // channels. It is safe for concurrent use by a single goroutine. type Backoff struct { base time.Duration max time.Duration attempts int } // NewBackoff creates a backoff with the given base and cap. A non-positive base // defaults to 1s and a non-positive cap to 60s. func NewBackoff(base, cap time.Duration) *Backoff { if base <= 0 { base = time.Second } if cap <= 0 { cap = 60 * time.Second } return &Backoff{base: base, max: cap} } // Next returns the delay to sleep before the next attempt and advances the // internal attempt counter. It never exceeds the configured cap. func (b *Backoff) Next() time.Duration { var d time.Duration if b.attempts >= 60 { d = b.max } else { d = b.base << uint(b.attempts) } if d > b.max || d <= 0 { d = b.max } b.attempts++ return d } // Reset restores the backoff to its initial state after a successful connect, so // a later drop starts the ramp over from the base delay. func (b *Backoff) Reset() { b.attempts = 0 }