@@ -13,6 +13,7 @@ import (
1313 "path/filepath"
1414 "strings"
1515 "sync"
16+ "time"
1617
1718 "github.com/mdp/qrterminal/v3"
1819 _ "modernc.org/sqlite"
@@ -35,16 +36,24 @@ import (
3536const (
3637 sqliteDriver = "sqlite"
3738 whatsappDBName = "store.db"
39+
40+ reconnectInitial = 5 * time .Second
41+ reconnectMax = 5 * time .Minute
42+ reconnectMultiplier = 2.0
3843)
3944
4045// WhatsAppNativeChannel implements the WhatsApp channel using whatsmeow (in-process, no external bridge).
4146type WhatsAppNativeChannel struct {
4247 * BaseChannel
43- config config.WhatsAppConfig
44- storePath string
45- client * whatsmeow.Client
46- container * sqlstore.Container
47- mu sync.Mutex
48+ config config.WhatsAppConfig
49+ storePath string
50+ client * whatsmeow.Client
51+ container * sqlstore.Container
52+ mu sync.Mutex
53+ runCtx context.Context
54+ runCancel context.CancelFunc
55+ reconnectMu sync.Mutex
56+ reconnecting bool
4857}
4958
5059// NewWhatsAppNativeChannel creates a WhatsApp channel that uses whatsmeow for connection.
@@ -133,13 +142,17 @@ func (c *WhatsAppNativeChannel) Start(ctx context.Context) error {
133142 }
134143 }
135144
145+ c .runCtx , c .runCancel = context .WithCancel (ctx )
136146 c .setRunning (true )
137147 logger .InfoCF ("channels" , "WhatsApp native channel connected" , nil )
138148 return nil
139149}
140150
141151func (c * WhatsAppNativeChannel ) Stop (ctx context.Context ) error {
142152 logger .InfoCF ("channels" , "Stopping WhatsApp native channel" , nil )
153+ if c .runCancel != nil {
154+ c .runCancel ()
155+ }
143156 c .mu .Lock ()
144157 client := c .client
145158 container := c .container
@@ -158,9 +171,65 @@ func (c *WhatsAppNativeChannel) Stop(ctx context.Context) error {
158171}
159172
160173func (c * WhatsAppNativeChannel ) eventHandler (evt interface {}) {
161- switch v := evt .(type ) {
174+ switch evt .(type ) {
162175 case * events.Message :
163- c .handleIncoming (v )
176+ c .handleIncoming (evt .(* events.Message ))
177+ case * events.Disconnected :
178+ logger .InfoCF ("channels" , "WhatsApp disconnected, will attempt reconnection" , nil )
179+ c .reconnectMu .Lock ()
180+ if c .reconnecting {
181+ c .reconnectMu .Unlock ()
182+ return
183+ }
184+ c .reconnecting = true
185+ c .reconnectMu .Unlock ()
186+ go c .reconnectWithBackoff ()
187+ }
188+ }
189+
190+ func (c * WhatsAppNativeChannel ) reconnectWithBackoff () {
191+ defer func () {
192+ c .reconnectMu .Lock ()
193+ c .reconnecting = false
194+ c .reconnectMu .Unlock ()
195+ }()
196+
197+ backoff := reconnectInitial
198+ for {
199+ select {
200+ case <- c .runCtx .Done ():
201+ return
202+ default :
203+ }
204+
205+ c .mu .Lock ()
206+ client := c .client
207+ c .mu .Unlock ()
208+ if client == nil {
209+ return
210+ }
211+
212+ logger .InfoCF ("channels" , "WhatsApp reconnecting" , map [string ]any {"backoff" : backoff .String ()})
213+ err := client .Connect ()
214+ if err == nil {
215+ logger .InfoCF ("channels" , "WhatsApp reconnected" , nil )
216+ return
217+ }
218+
219+ logger .WarnCF ("channels" , "WhatsApp reconnect failed" , map [string ]any {"error" : err .Error ()})
220+
221+ select {
222+ case <- c .runCtx .Done ():
223+ return
224+ case <- time .After (backoff ):
225+ if backoff < reconnectMax {
226+ next := time .Duration (float64 (backoff ) * reconnectMultiplier )
227+ if next > reconnectMax {
228+ next = reconnectMax
229+ }
230+ backoff = next
231+ }
232+ }
164233 }
165234}
166235
0 commit comments