From a0f1fedb9fe129147d33abbc22b1c6a941b56170 Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Wed, 5 Aug 2026 00:24:57 +0530 Subject: [PATCH] Use whatsmeow auto-reconnect exclusively --- whatsapp/client.go | 44 ++++++++----------------------- whatsapp/client_lifecycle_test.go | 39 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 whatsapp/client_lifecycle_test.go diff --git a/whatsapp/client.go b/whatsapp/client.go index 336871f..9e74a8f 100644 --- a/whatsapp/client.go +++ b/whatsapp/client.go @@ -91,6 +91,14 @@ func (c *Client) QRChannel() <-chan string { return c.qrChan } +// configureClient makes whatsmeow the sole owner of socket reconnection. +// Connection events below only update application state; they must not start a +// second reconnect loop that can race whatsmeow's built-in auto-reconnect. +func (c *Client) configureClient(client *whatsmeow.Client) { + client.EnableAutoReconnect = true + client.BackgroundEventCtx = c.appCtx +} + func (c *Client) Connect(ctx context.Context) error { container, err := c.getContainer() if err != nil { @@ -103,6 +111,7 @@ func (c *Client) Connect(ctx context.Context) error { } client := whatsmeow.NewClient(deviceStore, waLog.Noop) + c.configureClient(client) c.mu.Lock() c.wa = client c.mu.Unlock() @@ -156,6 +165,7 @@ func (c *Client) Login(ctx context.Context) (<-chan string, error) { deviceStore := container.NewDevice() client := whatsmeow.NewClient(deviceStore, waLog.Noop) + c.configureClient(client) client.AddEventHandler(c.handleEvent) qrCodes := make(chan string, 5) @@ -213,42 +223,10 @@ func (c *Client) handleEvent(rawEvt interface{}) { c.connected = true c.mu.Unlock() case *events.Disconnected: - log.Println("WhatsApp disconnected") + log.Println("WhatsApp disconnected; waiting for whatsmeow auto-reconnect") c.mu.Lock() c.connected = false - client := c.wa c.mu.Unlock() - if client != nil { - go c.reconnect(client) - } - } -} - -func (c *Client) reconnect(client *whatsmeow.Client) { - backoff := 5 * time.Second - maxBackoff := 5 * time.Minute - for { - select { - case <-c.appCtx.Done(): - return - case <-time.After(backoff): - } - - c.mu.RLock() - current := c.wa - c.mu.RUnlock() - if current != client { - return - } - - log.Printf("attempting WhatsApp reconnect...") - if err := client.Connect(); err != nil { - log.Printf("reconnect failed: %v", err) - backoff = min(backoff*2, maxBackoff) - continue - } - log.Println("WhatsApp reconnected") - return } } diff --git a/whatsapp/client_lifecycle_test.go b/whatsapp/client_lifecycle_test.go new file mode 100644 index 0000000..742ccd0 --- /dev/null +++ b/whatsapp/client_lifecycle_test.go @@ -0,0 +1,39 @@ +package whatsapp + +import ( + "context" + "testing" + + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types/events" +) + +func TestConfigureClientDelegatesReconnectsToWhatsmeow(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + c := &Client{appCtx: ctx} + wa := &whatsmeow.Client{} + c.configureClient(wa) + + if !wa.EnableAutoReconnect { + t.Fatal("whatsmeow auto-reconnect is disabled") + } + if wa.BackgroundEventCtx != ctx { + t.Fatal("whatsmeow reconnect lifecycle is not bound to the application context") + } +} + +func TestConnectionEventsOnlyUpdateApplicationState(t *testing.T) { + c := &Client{} + + c.handleEvent(&events.Connected{}) + if !c.IsConnected() { + t.Fatal("Connected event did not mark the client connected") + } + + c.handleEvent(&events.Disconnected{}) + if c.IsConnected() { + t.Fatal("Disconnected event did not mark the client disconnected") + } +}