diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/qrchan.go')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/qrchan.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/qrchan.go b/vendor/go.mau.fi/whatsmeow/qrchan.go new file mode 100644 index 00000000..c96b2188 --- /dev/null +++ b/vendor/go.mau.fi/whatsmeow/qrchan.go @@ -0,0 +1,168 @@ +// Copyright (c) 2021 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package whatsmeow + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "go.mau.fi/whatsmeow/types/events" + waLog "go.mau.fi/whatsmeow/util/log" +) + +type QRChannelItem struct { + // The type of event, "code" for new QR codes. + // For non-code/error events, you can just compare the whole item to the event variables (like QRChannelSuccess). + Event string + // If the item is a pair error, then this field contains the error message. + Error error + // If the item is a new code, then this field contains the raw data. + Code string + // The timeout after which the next code will be sent down the channel. + Timeout time.Duration +} + +var ( + // QRChannelSuccess is emitted from GetQRChannel when the pairing is successful. + QRChannelSuccess = QRChannelItem{Event: "success"} + // QRChannelTimeout is emitted from GetQRChannel if the socket gets disconnected by the server before the pairing is successful. + QRChannelTimeout = QRChannelItem{Event: "timeout"} + // QRChannelErrUnexpectedEvent is emitted from GetQRChannel if an unexpected connection event is received, + // as that likely means that the pairing has already happened before the channel was set up. + QRChannelErrUnexpectedEvent = QRChannelItem{Event: "err-unexpected-state"} + // QRChannelScannedWithoutMultidevice is emitted from GetQRChannel if events.QRScannedWithoutMultidevice is received. + QRChannelScannedWithoutMultidevice = QRChannelItem{Event: "err-scanned-without-multidevice"} +) + +type qrChannel struct { + sync.Mutex + cli *Client + log waLog.Logger + ctx context.Context + handlerID uint32 + closed uint32 + output chan<- QRChannelItem + stopQRs chan struct{} +} + +func (qrc *qrChannel) emitQRs(evt *events.QR) { + var nextCode string + for { + if len(evt.Codes) == 0 { + if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) { + qrc.log.Debugf("Ran out of QR codes, closing channel with status %s and disconnecting client", QRChannelTimeout) + qrc.output <- QRChannelTimeout + close(qrc.output) + go qrc.cli.RemoveEventHandler(qrc.handlerID) + qrc.cli.Disconnect() + } else { + qrc.log.Debugf("Ran out of QR codes, but channel is already closed") + } + return + } else if atomic.LoadUint32(&qrc.closed) == 1 { + qrc.log.Debugf("QR code channel is closed, exiting QR emitter") + return + } + timeout := 20 * time.Second + if len(evt.Codes) == 6 { + timeout = 60 * time.Second + } + nextCode, evt.Codes = evt.Codes[0], evt.Codes[1:] + qrc.log.Debugf("Emitting QR code %s", nextCode) + select { + case qrc.output <- QRChannelItem{Code: nextCode, Timeout: timeout, Event: "code"}: + default: + qrc.log.Debugf("Output channel didn't accept code, exiting QR emitter") + if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) { + close(qrc.output) + go qrc.cli.RemoveEventHandler(qrc.handlerID) + qrc.cli.Disconnect() + } + return + } + select { + case <-time.After(timeout): + case <-qrc.stopQRs: + qrc.log.Debugf("Got signal to stop QR emitter") + return + case <-qrc.ctx.Done(): + qrc.log.Debugf("Context is done, stopping QR emitter") + if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) { + close(qrc.output) + go qrc.cli.RemoveEventHandler(qrc.handlerID) + qrc.cli.Disconnect() + } + } + } +} + +func (qrc *qrChannel) handleEvent(rawEvt interface{}) { + if atomic.LoadUint32(&qrc.closed) == 1 { + qrc.log.Debugf("Dropping event of type %T, channel is closed", rawEvt) + return + } + var outputType QRChannelItem + switch evt := rawEvt.(type) { + case *events.QR: + qrc.log.Debugf("Received QR code event, starting to emit codes to channel") + go qrc.emitQRs(evt) + return + case *events.QRScannedWithoutMultidevice: + qrc.log.Debugf("QR code scanned without multidevice enabled") + qrc.output <- QRChannelScannedWithoutMultidevice + return + case *events.PairSuccess: + outputType = QRChannelSuccess + case *events.PairError: + outputType = QRChannelItem{ + Event: "error", + Error: evt.Error, + } + case *events.Disconnected: + outputType = QRChannelTimeout + case *events.Connected, *events.ConnectFailure, *events.LoggedOut: + outputType = QRChannelErrUnexpectedEvent + default: + return + } + close(qrc.stopQRs) + if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) { + qrc.log.Debugf("Closing channel with status %+v", outputType) + qrc.output <- outputType + close(qrc.output) + } else { + qrc.log.Debugf("Got status %+v, but channel is already closed", outputType) + } + // Has to be done in background because otherwise there's a deadlock with eventHandlersLock + go qrc.cli.RemoveEventHandler(qrc.handlerID) +} + +// GetQRChannel returns a channel that automatically outputs a new QR code when the previous one expires. +// +// This must be called *before* Connect(). It will then listen to all the relevant events from the client. +// +// The last value to be emitted will be a special string, either "success", "timeout" or "err-already-have-id", +// depending on the result of the pairing. The channel will be closed immediately after one of those. +func (cli *Client) GetQRChannel(ctx context.Context) (<-chan QRChannelItem, error) { + if cli.IsConnected() { + return nil, ErrQRAlreadyConnected + } else if cli.Store.ID != nil { + return nil, ErrQRStoreContainsID + } + ch := make(chan QRChannelItem, 8) + qrc := qrChannel{ + output: ch, + stopQRs: make(chan struct{}), + cli: cli, + log: cli.Log.Sub("QRChannel"), + ctx: ctx, + } + qrc.handlerID = cli.AddEventHandler(qrc.handleEvent) + return ch, nil +} |