diff --git a/docs/env.md b/docs/env.md index f641c7b..9c52239 100644 --- a/docs/env.md +++ b/docs/env.md @@ -15,6 +15,24 @@ WAHA_CLIENT_BROWSER_NAME=Firefox WAHA_CLIENT_DEVICE_NAME=Ubuntu ``` +## Keepalive + +whatsmeow pings the websocket on a random interval in `[min, max)` (default 20-30s) +to keep the connection alive. Some proxies (residential/rotating) reap an idle +tunnel sooner than that, so the ping lands on an already-dead socket every time, +producing constant `Keepalive timed out` → reconnect loops (session flapping). + +Lower the interval below the proxy's idle-reap window to keep the tunnel warm. +Both values are in **seconds**; `0` or unset leaves the whatsmeow default. +`max` must be strictly greater than `min` — if it isn't, `max` is adjusted to +`min + 10s` and a warning is logged. + +```bash +# Ping every 8-12s instead of the default 20-30s +WAHA_GOWS_KEEPALIVE_INTERVAL_MIN_SEC=8 +WAHA_GOWS_KEEPALIVE_INTERVAL_MAX_SEC=12 +``` + ## GOWS Device Props Low-level device capability flags sent to WhatsApp during session linking. diff --git a/src/env.go b/src/env.go index e8f4da5..3b63b02 100644 --- a/src/env.go +++ b/src/env.go @@ -18,6 +18,24 @@ func getClientConfig() ClientConfig { return cfg } +// KeepAliveConfig overrides whatsmeow's websocket keepalive ping interval. +// Both values are in seconds; 0 (or unset) leaves whatsmeow's default +// (min 20s / max 30s) in place. Useful behind proxies that reap idle tunnels +// faster than the default ping, which otherwise causes constant +// "Keepalive timed out" reconnect loops. +type KeepAliveConfig struct { + IntervalMinSec uint32 `env:"WAHA_GOWS_KEEPALIVE_INTERVAL_MIN_SEC"` + IntervalMaxSec uint32 `env:"WAHA_GOWS_KEEPALIVE_INTERVAL_MAX_SEC"` +} + +func getKeepAliveConfig() KeepAliveConfig { + cfg := KeepAliveConfig{} + if err := env.Parse(&cfg); err != nil { + panic(err) + } + return cfg +} + // DevicePropsConfig holds optional overrides for waCompanionReg.DeviceProps. // Each Maybe field has three states: // diff --git a/src/env_test.go b/src/env_test.go index 243374a..b949144 100644 --- a/src/env_test.go +++ b/src/env_test.go @@ -117,3 +117,22 @@ func TestFullSyncDaysLimit_Value(t *testing.T) { t.Fatalf("expected Value=30, got %v", cfg.FullSyncDaysLimit.Value) } } + +func TestKeepAliveConfig_NotSet(t *testing.T) { + cfg := getKeepAliveConfig() + if cfg.IntervalMinSec != 0 || cfg.IntervalMaxSec != 0 { + t.Fatalf("expected 0/0 when unset, got %d/%d", cfg.IntervalMinSec, cfg.IntervalMaxSec) + } +} + +func TestKeepAliveConfig_Values(t *testing.T) { + t.Setenv("WAHA_GOWS_KEEPALIVE_INTERVAL_MIN_SEC", "8") + t.Setenv("WAHA_GOWS_KEEPALIVE_INTERVAL_MAX_SEC", "12") + cfg := getKeepAliveConfig() + if cfg.IntervalMinSec != 8 { + t.Fatalf("expected min=8, got %d", cfg.IntervalMinSec) + } + if cfg.IntervalMaxSec != 12 { + t.Fatalf("expected max=12, got %d", cfg.IntervalMaxSec) + } +} diff --git a/src/main.go b/src/main.go index 0bcfeb6..b3f24f2 100644 --- a/src/main.go +++ b/src/main.go @@ -8,6 +8,7 @@ import ( "github.com/devlikeapro/gows/server" "github.com/devlikeapro/gows/wrpc" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" + "go.mau.fi/whatsmeow" waLog "go.mau.fi/whatsmeow/util/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -99,6 +100,25 @@ func remove(path string) { _ = os.Remove(path) } +func applyKeepAliveConfig(log waLog.Logger, cfg KeepAliveConfig) { + if cfg.IntervalMinSec == 0 && cfg.IntervalMaxSec == 0 { + return + } + if cfg.IntervalMinSec > 0 { + whatsmeow.KeepAliveIntervalMin = time.Duration(cfg.IntervalMinSec) * time.Second + } + if cfg.IntervalMaxSec > 0 { + whatsmeow.KeepAliveIntervalMax = time.Duration(cfg.IntervalMaxSec) * time.Second + } + // whatsmeow picks a random interval in [min, max); max must be strictly + // greater than min or rand.Int64N panics at ping time. + if whatsmeow.KeepAliveIntervalMax <= whatsmeow.KeepAliveIntervalMin { + whatsmeow.KeepAliveIntervalMax = whatsmeow.KeepAliveIntervalMin + 10*time.Second + log.Warnf("Keepalive max interval <= min; adjusted max to %s", whatsmeow.KeepAliveIntervalMax) + } + log.Infof("Using keepalive ping interval: min=%s max=%s", whatsmeow.KeepAliveIntervalMin, whatsmeow.KeepAliveIntervalMax) +} + func main() { flag.Parse() log := gowsLog.Stdout("Server", "DEBUG", false) @@ -118,6 +138,8 @@ func main() { log.Infof("Using device name: '%s', browser name: '%s'", clientCfg.DeviceName, clientCfg.BrowserName) gows.SetDeviceAndBrowser(clientCfg.DeviceName, clientCfg.BrowserName) + applyKeepAliveConfig(log, getKeepAliveConfig()) + // Build the server grpcServer := buildGrpcServer(log) // Open unix socket