mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 18:18:37 +00:00
fix(zalo): allow QR session restart with race-safe cancel (#346)
Cancel previous QR session when user retries instead of blocking with "QR session already active". Uses sync.Map.Swap for atomic replacement and CompareAndDelete so old goroutines don't clobber new sessions.
This commit is contained in:
1 parent
cd022699f6
commit
a9a40a3046
1 file changed
+13
-12
@@ -22,7 +22,7 @@ import (
|
||||
type QRMethods struct {
|
||||
instanceStore store.ChannelInstanceStore
|
||||
msgBus *bus.MessageBus
|
||||
activeSessions sync.Map // instanceID (string) -> struct{}
|
||||
activeSessions sync.Map // instanceID (string) -> context.CancelFunc
|
||||
}
|
||||
|
||||
func NewQRMethods(s store.ChannelInstanceStore, msgBus *bus.MessageBus) *QRMethods {
|
||||
@@ -53,27 +53,28 @@ func (m *QRMethods) handleQRStart(ctx context.Context, client *gateway.Client, r
|
||||
return
|
||||
}
|
||||
|
||||
if _, loaded := m.activeSessions.LoadOrStore(params.InstanceID, struct{}{}); loaded {
|
||||
client.SendResponse(goclawprotocol.NewErrorResponse(req.ID, goclawprotocol.ErrInvalidRequest, "QR session already active for this instance"))
|
||||
return
|
||||
qrCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
|
||||
// Atomically swap cancel func; cancel any previous QR session so the user can retry.
|
||||
if prev, loaded := m.activeSessions.Swap(params.InstanceID, cancel); loaded {
|
||||
if cancelFn, ok := prev.(context.CancelFunc); ok {
|
||||
cancelFn()
|
||||
}
|
||||
}
|
||||
|
||||
// ACK immediately — QR arrives via event.
|
||||
client.SendResponse(goclawprotocol.NewOKResponse(req.ID, map[string]any{"status": "started"}))
|
||||
|
||||
go m.runQRFlow(ctx, client, params.InstanceID, instID)
|
||||
go m.runQRFlow(qrCtx, cancel, client, params.InstanceID, instID)
|
||||
}
|
||||
|
||||
func (m *QRMethods) runQRFlow(ctx context.Context, client *gateway.Client, instanceIDStr string, instanceID uuid.UUID) {
|
||||
defer m.activeSessions.Delete(instanceIDStr)
|
||||
func (m *QRMethods) runQRFlow(ctx context.Context, cancel context.CancelFunc, client *gateway.Client, instanceIDStr string, instanceID uuid.UUID) {
|
||||
defer cancel()
|
||||
defer m.activeSessions.CompareAndDelete(instanceIDStr, cancel)
|
||||
|
||||
sess := protocol.NewSession()
|
||||
// LoginQR has internal 100s timeout per QR code. Use 2m as outer bound.
|
||||
// Derived from parent ctx so QR flow cancels when the WS client disconnects.
|
||||
qrCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
cred, err := protocol.LoginQR(qrCtx, sess, func(qrPNG []byte) {
|
||||
cred, err := protocol.LoginQR(ctx, sess, func(qrPNG []byte) {
|
||||
client.SendEvent(goclawprotocol.EventFrame{
|
||||
Type: goclawprotocol.FrameTypeEvent,
|
||||
Event: goclawprotocol.EventZaloPersonalQRCode,
|
||||
|
||||
Reference in new issue
Block a user