Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions src/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
//
Expand Down
19 changes: 19 additions & 0 deletions src/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
22 changes: 22 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down