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
44 changes: 11 additions & 33 deletions whatsapp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}

Expand Down
39 changes: 39 additions & 0 deletions whatsapp/client_lifecycle_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}