]> git.giacomolli.uk - onechat/commitdiff
Keep retries after a rejected pair request
authorAnderson A. Giacomolli <agiacomolli@gmail.com>
Mon, 9 Nov 2026 18:12:48 +0000 (15:12 -0300)
committerAnderson A. Giacomolli <agiacomolli@gmail.com>
Mon, 9 Nov 2026 18:12:48 +0000 (15:12 -0300)
Keep starting connection retries active when a pair request finds an
existing link. Only the unlink request stops the retry timer, while
logout keeps its stop path. Rejecting a pair request during an outage
must not leave WhatsApp disconnected until a restart.

README.rst
whatsapp.go
whatsapp_test.go

index 0cc4b1e194d9a7deaf3a32d8281b1c0bbc4b67de..1c504bfba9d56e0b407564b61e4727f167f4d89f 100644 (file)
@@ -359,14 +359,14 @@ memory tree summarizes it with model calls, which use the quota.
 When the session holds a linked device, the daemon connects at its
 start. If the first connection fails, it tries again after 2 seconds,
 then doubles the wait up to 5 minutes. The inbox and commands stay
-active during each wait. An unlink, logout, or pair command stops these
-retries. After a network failure of an open connection, the library
-connects again by itself. When a disconnection lasts 10 minutes, all
-clients get one notice, and one more when the connection comes back.
-When WhatsApp ends the connection for good, for example because another
-program uses the same session, all clients get a notice at once. When
-the user removes the device in the phone, the daemon deletes the session
-and tells all clients one time.
+active during each wait. An unlink or a logout stops these retries.
+After a network failure of an open connection, the library connects
+again by itself. When a disconnection lasts 10 minutes, all clients get
+one notice, and one more when the connection comes back. When WhatsApp
+ends the connection for good, for example because another program uses
+the same session, all clients get a notice at once. When the user
+removes the device in the phone, the daemon deletes the session and
+tells all clients one time.
 
 ***********
  Schedules
index e02e8c66242c1953091e1a85eeb78b691f9496a0..c3db573f9c8d332ab885e662fd2b6e4e07b189f5 100644 (file)
@@ -209,10 +209,10 @@ type WhatsApp struct {
 // failed first connection starts the same outage notice as a lost connection. A
 // timer in the select retries it with a doubled wait from 2 seconds to 5
 // minutes, without a retry goroutine or a wait that blocks commands or the
-// inbox. A connection, unlink, logout, pair command, or ctx end stops retries.
-// The library owns retries after a connection opens. Run returns the error of a
-// write of the log or of the state file, which stops the daemon, because then
-// messages would be lost.
+// inbox. A connection, unlink, logout, or ctx end stops retries. The library
+// owns retries after a connection opens. Run returns the error of a write of
+// the log or of the state file, which stops the daemon, because then messages
+// would be lost.
 func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) error {
        w.mutex.Lock()
        w.notify = notify
@@ -277,13 +277,13 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err
                                return err
                        }
                case request := <-w.requests:
-                       stopRetry()
                        cancel()
                        pairing.Wait()
                        cancel = func() {}
 
                        switch {
                        case request.unlink:
+                               stopRetry()
                                err = w.unlink(ctx, l, request.answer)
                                if err != nil {
                                        return err
index c14571b61211412693e064d5bc12befa065367fe..db04dc7424737406782dbb439f365b3db0279e39 100644 (file)
@@ -106,6 +106,35 @@ func TestWhatsAppFirstConnectionRetriesStopWhenContextEnds(t *testing.T) {
        })
 }
 
+func TestWhatsAppLinkedPairRequestKeepsFirstConnectionRetries(t *testing.T) {
+       synctest.Test(t, func(t *testing.T) {
+               test := whatsappRetryTest(t)
+               test.fake.connectErr = errors.New("network unavailable")
+               test.start(t)
+               synctest.Wait()
+
+               answers := make(chan string, 1)
+               test.whatsapp.Command("pair 15550001111", func(text string) {
+                       answers <- text
+               })
+               require.Equal(t, "WhatsApp is linked already. Run /whatsapp unlink first.", <-answers)
+               synctest.Wait()
+               test.fake.mutex.Lock()
+               test.fake.connectErr = nil
+               test.fake.mutex.Unlock()
+               time.Sleep(whatsappRetryDelay)
+               synctest.Wait()
+               require.Len(t, test.fake.Connections(), 2)
+               require.True(t, test.fake.Emit(&events.Connected{}))
+               require.True(t, test.fake.Emit(&events.OfflineSyncCompleted{}))
+               require.Contains(t, test.whatsapp.Status(), "linked and connected since")
+               time.Sleep(whatsappRetryLimit)
+               synctest.Wait()
+               require.Len(t, test.fake.Connections(), 2)
+               require.Equal(t, "first connect failed, retrying: network unavailable\nconnected again after 2s\n", test.logs.String())
+       })
+}
+
 func TestWhatsAppFirstConnectionRetriesStopForLinkChanges(t *testing.T) {
        testCases := []struct {
                name string
@@ -123,12 +152,6 @@ func TestWhatsAppFirstConnectionRetriesStopForLinkChanges(t *testing.T) {
                                test.fake.Emit(&events.LoggedOut{})
                        },
                },
-               {
-                       name: "pair command",
-                       stop: func(test *WhatsAppRetryTest) {
-                               test.whatsapp.Command("pair 15550001111", func(text string) {})
-                       },
-               },
                {
                        name: "permanent disconnect",
                        stop: func(test *WhatsAppRetryTest) {