From: Anderson A. Giacomolli Date: Sun, 8 Nov 2026 17:50:26 +0000 (-0300) Subject: Keep the connection of a new WhatsApp link X-Git-Url: http://git.giacomolli.uk/onechat/commitdiff_plain/a85d214071706753f358f0af23cfd525c28dbfe0 Keep the connection of a new WhatsApp link After a pairing, the linked device never connected again. The pairing client connected with the context of the pairing, and the library keeps the context of ConnectContext for its keep-alive loop, its event handler loop, and its reconnections. After PairSuccess the server ends the stream and the library connects again with that context, but the end of the pairing canceled it, so the reconnection stopped. The session got no prekeys, no Signal sessions, and no contacts, and no message came. The pairing client now connects with the context of Run, which lives until the daemon stops, as the connection at the start of the daemon does. The context of the pairing still limits the QR channel, the wait for the code, and the wait for the link, and a pairing that does not link disconnects its client. The fake client keeps the context of each connection, and the pairing test checks that the context stays alive after the link. The test fails without the fix. Issue: 6f69ca1 --- diff --git a/whatsapp.go b/whatsapp.go index 4268a16..42b95a8 100644 --- a/whatsapp.go +++ b/whatsapp.go @@ -330,7 +330,7 @@ func (w *WhatsApp) Run(ctx context.Context, l *TurnLoop, notify func(Event)) err pairing.Go(func() { defer cancelPair() - w.pair(pairCtx, l, request.phone, request.answer) + w.pair(pairCtx, ctx, l, request.phone, request.answer) }) } } @@ -797,7 +797,13 @@ func (w *WhatsApp) removeSession() error { // number, an expired code, or a failure. A pairing that a new one ends gives no // answer. A link notes "WhatsApp is linked." in the log of l, with no number. // The client of a pairing that does not link is disconnected. -func (w *WhatsApp) pair(ctx context.Context, l *TurnLoop, phone string, answer func(text string)) { +// +// ctx limits the pairing, but the connection uses runCtx, the context of Run. +// The library keeps the context of the connection for its keep-alive, its event +// handler, and its reconnections, and after the link the server ends the stream +// and the library connects again. With ctx, the end of the pairing would stop +// that reconnection, and the linked device would never connect. +func (w *WhatsApp) pair(ctx, runCtx context.Context, l *TurnLoop, phone string, answer func(text string)) { device, err := w.newDevice(ctx) if err != nil { answer("The pairing failed: " + err.Error()) @@ -814,7 +820,7 @@ func (w *WhatsApp) pair(ctx context.Context, l *TurnLoop, phone string, answer f qr, err := client.GetQRChannel(ctx) if err == nil { - err = client.ConnectContext(ctx) + err = client.ConnectContext(runCtx) } if err != nil { diff --git a/whatsapp_fake_helper_test.go b/whatsapp_fake_helper_test.go index e463cd0..c9bc835 100644 --- a/whatsapp_fake_helper_test.go +++ b/whatsapp_fake_helper_test.go @@ -67,28 +67,33 @@ var ( // WhatsAppFake is a fake WhatsApp client. It keeps the event handler that the // daemon gives it, and Emit gives events to the handler, as the library does. -// Each connection sends to connects, and each pairing sends its channel to -// pairings, so that the test gives the items of the pairing. A connection fails -// with connectErr, a logout with logoutErr, and a send with sendErr. groups are -// the groups of the account. A send that does not fail gets the time -// whatsappTestSendTime. +// Each connection keeps its context and sends to connects, and each pairing +// sends its channel to pairings, so that the test gives the items of the +// pairing. A connection fails with connectErr, a logout with logoutErr, and a +// send with sendErr. groups are the groups of the account. A send that does not +// fail gets the time whatsappTestSendTime. type WhatsAppFake struct { connects chan struct{} pairings chan chan whatsmeow.QRChannelItem - mutex sync.Mutex - handle func(event any) bool - connectErr error - logoutErr error - groups []*types.GroupInfo - sendErr error - phones []string - logouts int - sent []WhatsAppFakeSend + mutex sync.Mutex + handle func(event any) bool + connectErr error + logoutErr error + groups []*types.GroupInfo + sendErr error + phones []string + logouts int + sent []WhatsAppFakeSend + connections []context.Context } -// ConnectContext tells the test that the daemon connected. +// ConnectContext keeps ctx and tells the test that the daemon connected. func (f *WhatsAppFake) ConnectContext(ctx context.Context) error { + f.mutex.Lock() + f.connections = append(f.connections, ctx) + f.mutex.Unlock() + f.connects <- struct{}{} f.mutex.Lock() @@ -97,6 +102,14 @@ func (f *WhatsAppFake) ConnectContext(ctx context.Context) error { return f.connectErr } +// Connections returns the contexts of the connections, in order. +func (f *WhatsAppFake) Connections() []context.Context { + f.mutex.Lock() + defer f.mutex.Unlock() + + return slices.Clone(f.connections) +} + // Disconnect does nothing. func (f *WhatsAppFake) Disconnect() { } diff --git a/whatsapp_test.go b/whatsapp_test.go index 0ba2714..791e3ca 100644 --- a/whatsapp_test.go +++ b/whatsapp_test.go @@ -140,6 +140,14 @@ func TestWhatsAppPairsWithACode(t *testing.T) { items <- whatsmeow.QRChannelSuccess asking.WaitFor(t, "WhatsApp is linked to +15550001111.") + // The library connects again after the link with the context of the + // connection, so the end of the pairing must not end it. + connections := fake.Connections() + require.Len(t, connections, 1) + require.Never(t, func() bool { + return connections[0].Err() != nil + }, 200*time.Millisecond, time.Millisecond) + require.Equal(t, []string{whatsappTestNumber}, fake.Phones()) require.Equal(t, "WhatsApp is linked and not connected yet.", whatsapp.Status()) require.Equal(t, "[connected to OneChat]\n", other.Output())