66 "log/slog"
77
88 "go.mau.fi/whatsmeow"
9+ wastore "go.mau.fi/whatsmeow/store"
10+ "go.mau.fi/whatsmeow/types"
911)
1012
1113// StartQRFlow initiates the QR authentication flow.
@@ -16,35 +18,26 @@ import (
1618func (c * Channel ) StartQRFlow (ctx context.Context ) (<- chan whatsmeow.QRChannelItem , error ) {
1719 c .reauthMu .Lock ()
1820 defer c .reauthMu .Unlock ()
19- if c .client == nil {
20- // Lazy init: wizard may request QR before Start() is called.
21- c .mu .Lock ()
22- if c .client == nil {
23- if c .ctx == nil {
24- c .ctx , c .cancel = context .WithCancel (context .Background ())
25- }
26- deviceStore , err := c .container .GetFirstDevice (ctx )
27- if err != nil {
28- c .mu .Unlock ()
29- return nil , fmt .Errorf ("whatsapp get device: %w" , err )
30- }
31- c .client = whatsmeow .NewClient (deviceStore , nil )
32- c .client .AddEventHandler (c .handleEvent )
33- }
21+
22+ c .mu .Lock ()
23+ if err := c .ensureQRClientLocked (ctx ); err != nil {
3424 c .mu .Unlock ()
25+ return nil , fmt .Errorf ("whatsapp get device: %w" , err )
3526 }
27+ client := c .client
28+ c .mu .Unlock ()
3629
3730 if c .IsAuthenticated () {
3831 return nil , nil // caller checks this
3932 }
4033
41- qrChan , err := c . client .GetQRChannel (ctx )
34+ qrChan , err := client .GetQRChannel (ctx )
4235 if err != nil {
4336 return nil , fmt .Errorf ("whatsapp get QR channel: %w" , err )
4437 }
4538
46- if ! c . client .IsConnected () {
47- if err := c . client .Connect (); err != nil {
39+ if ! client .IsConnected () {
40+ if err := client .Connect (); err != nil {
4841 return nil , fmt .Errorf ("whatsapp connect for QR: %w" , err )
4942 }
5043 }
@@ -90,13 +83,89 @@ func (c *Channel) Reauth() error {
9083 }
9184 c .ctx , c .cancel = context .WithCancel (parent )
9285
93- // Re-create client with fresh device store.
94- deviceStore , err := c .container .GetFirstDevice (context .Background ())
95- if err != nil {
86+ if err := c .resetClientLocked (context .Background ()); err != nil {
9687 return fmt .Errorf ("whatsapp: get fresh device: %w" , err )
9788 }
89+
90+ return nil
91+ }
92+
93+ // ensureQRClientLocked lazily creates or refreshes the client before QR login.
94+ // The caller must hold c.mu and c.reauthMu.
95+ func (c * Channel ) ensureQRClientLocked (ctx context.Context ) error {
96+ if c .client == nil {
97+ return c .resetClientLocked (ctx )
98+ }
99+ if ! c .client .Store .Deleted {
100+ return nil
101+ }
102+ c .lastQRMu .Lock ()
103+ c .waAuthenticated = false
104+ c .lastQRB64 = ""
105+ c .lastQRMu .Unlock ()
106+ return c .resetClientLocked (ctx )
107+ }
108+
109+ // resetClientLocked replaces the whatsmeow client while preserving the channel lifecycle.
110+ // The caller must hold c.mu.
111+ func (c * Channel ) resetClientLocked (ctx context.Context ) error {
112+ if ctx == nil {
113+ ctx = context .Background ()
114+ }
115+ if c .ctx == nil {
116+ parent := c .parentCtx
117+ if parent == nil {
118+ parent = context .Background ()
119+ }
120+ c .ctx , c .cancel = context .WithCancel (parent )
121+ }
122+ deviceStore , err := c .resolveDeviceStoreLocked (ctx )
123+ if err != nil {
124+ return err
125+ }
98126 c .client = whatsmeow .NewClient (deviceStore , nil )
99127 c .client .AddEventHandler (c .handleEvent )
100-
101128 return nil
102129}
130+
131+ func (c * Channel ) resolveDeviceStoreLocked (ctx context.Context ) (* wastore.Device , error ) {
132+ if ! c .deviceJID .IsEmpty () {
133+ deviceStore , err := c .container .GetDevice (ctx , c .deviceJID )
134+ if err != nil {
135+ return nil , err
136+ }
137+ if deviceStore != nil && ! deviceStore .Deleted {
138+ return deviceStore , nil
139+ }
140+ slog .Info ("whatsapp scoped device missing; creating fresh QR device" ,
141+ "channel" , c .Name (), "device_hash" , hashWhatsAppIdentifier (c .deviceJID .String ()))
142+ return c .container .NewDevice (), nil
143+ }
144+
145+ if c .legacyFirstDeviceFallback {
146+ devices , err := c .container .GetAllDevices (ctx )
147+ if err != nil {
148+ return nil , err
149+ }
150+ if len (devices ) > 0 {
151+ return devices [0 ], nil
152+ }
153+ }
154+
155+ return c .container .NewDevice (), nil
156+ }
157+
158+ func (c * Channel ) currentDeviceJID () types.JID {
159+ c .mu .Lock ()
160+ defer c .mu .Unlock ()
161+ if c .client == nil || c .client .Store == nil {
162+ return types .EmptyJID
163+ }
164+ return c .client .Store .GetJID ()
165+ }
166+
167+ func (c * Channel ) setDeviceJID (jid types.JID ) {
168+ c .mu .Lock ()
169+ defer c .mu .Unlock ()
170+ c .deviceJID = jid
171+ }
0 commit comments