qrchan.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // Copyright (c) 2022 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "context"
  9. "slices"
  10. "sync"
  11. "sync/atomic"
  12. "time"
  13. "go.mau.fi/whatsmeow/types/events"
  14. waLog "go.mau.fi/whatsmeow/util/log"
  15. )
  16. type QRChannelItem struct {
  17. // The type of event, "code" for new QR codes (see Code field) and "error" for pairing errors (see Error) field.
  18. // For non-code/error events, you can just compare the whole item to the event variables (like QRChannelSuccess).
  19. Event string
  20. // If the item is a pair error, then this field contains the error message.
  21. Error error
  22. // If the item is a new code, then this field contains the raw data.
  23. Code string
  24. // The timeout after which the next code will be sent down the channel.
  25. Timeout time.Duration
  26. }
  27. const QRChannelEventCode = "code"
  28. const QRChannelEventError = "error"
  29. // Possible final items in the QR channel. In addition to these, an `error` event may be emitted,
  30. // in which case the Error field will have the error that occurred during pairing.
  31. var (
  32. // QRChannelSuccess is emitted from GetQRChannel when the pairing is successful.
  33. QRChannelSuccess = QRChannelItem{Event: "success"}
  34. // QRChannelTimeout is emitted from GetQRChannel if the socket gets disconnected by the server before the pairing is successful.
  35. QRChannelTimeout = QRChannelItem{Event: "timeout"}
  36. // QRChannelErrUnexpectedEvent is emitted from GetQRChannel if an unexpected connection event is received,
  37. // as that likely means that the pairing has already happened before the channel was set up.
  38. QRChannelErrUnexpectedEvent = QRChannelItem{Event: "err-unexpected-state"}
  39. // QRChannelClientOutdated is emitted from GetQRChannel if events.ClientOutdated is received.
  40. QRChannelClientOutdated = QRChannelItem{Event: "err-client-outdated"}
  41. // QRChannelScannedWithoutMultidevice is emitted from GetQRChannel if events.QRScannedWithoutMultidevice is received.
  42. QRChannelScannedWithoutMultidevice = QRChannelItem{Event: "err-scanned-without-multidevice"}
  43. )
  44. type qrChannel struct {
  45. sync.Mutex
  46. cli *Client
  47. log waLog.Logger
  48. ctx context.Context
  49. handlerID uint32
  50. closed uint32
  51. output chan<- QRChannelItem
  52. stopQRs chan struct{}
  53. }
  54. func (qrc *qrChannel) emitQRs(codes []string) {
  55. var nextCode string
  56. for {
  57. if len(codes) == 0 {
  58. if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) {
  59. qrc.log.Debugf("Ran out of QR codes, closing channel with status %s and disconnecting client", QRChannelTimeout)
  60. qrc.output <- QRChannelTimeout
  61. close(qrc.output)
  62. go qrc.cli.RemoveEventHandler(qrc.handlerID)
  63. qrc.cli.Disconnect()
  64. } else {
  65. qrc.log.Debugf("Ran out of QR codes, but channel is already closed")
  66. }
  67. return
  68. } else if atomic.LoadUint32(&qrc.closed) == 1 {
  69. qrc.log.Debugf("QR code channel is closed, exiting QR emitter")
  70. return
  71. }
  72. timeout := 20 * time.Second
  73. if len(codes) == 6 {
  74. timeout = 60 * time.Second
  75. }
  76. nextCode, codes = codes[0], codes[1:]
  77. qrc.log.Debugf("Emitting QR code %s", nextCode)
  78. select {
  79. case qrc.output <- QRChannelItem{Code: nextCode, Timeout: timeout, Event: QRChannelEventCode}:
  80. default:
  81. qrc.log.Debugf("Output channel didn't accept code, exiting QR emitter")
  82. if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) {
  83. close(qrc.output)
  84. go qrc.cli.RemoveEventHandler(qrc.handlerID)
  85. qrc.cli.Disconnect()
  86. }
  87. return
  88. }
  89. select {
  90. case <-time.After(timeout):
  91. case <-qrc.stopQRs:
  92. qrc.log.Debugf("Got signal to stop QR emitter")
  93. return
  94. case <-qrc.ctx.Done():
  95. qrc.log.Debugf("Context is done, stopping QR emitter")
  96. if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) {
  97. close(qrc.output)
  98. go qrc.cli.RemoveEventHandler(qrc.handlerID)
  99. qrc.cli.Disconnect()
  100. }
  101. }
  102. }
  103. }
  104. func (qrc *qrChannel) handleEvent(rawEvt interface{}) {
  105. if atomic.LoadUint32(&qrc.closed) == 1 {
  106. qrc.log.Debugf("Dropping event of type %T, channel is closed", rawEvt)
  107. return
  108. }
  109. var outputType QRChannelItem
  110. switch evt := rawEvt.(type) {
  111. case *events.QR:
  112. qrc.log.Debugf("Received QR code event, starting to emit codes to channel")
  113. go qrc.emitQRs(slices.Clone(evt.Codes))
  114. return
  115. case *events.QRScannedWithoutMultidevice:
  116. qrc.log.Debugf("QR code scanned without multidevice enabled")
  117. qrc.output <- QRChannelScannedWithoutMultidevice
  118. return
  119. case *events.ClientOutdated:
  120. outputType = QRChannelClientOutdated
  121. case *events.PairSuccess:
  122. outputType = QRChannelSuccess
  123. case *events.PairError:
  124. outputType = QRChannelItem{
  125. Event: QRChannelEventError,
  126. Error: evt.Error,
  127. }
  128. case *events.Disconnected:
  129. outputType = QRChannelTimeout
  130. case *events.Connected, *events.ConnectFailure, *events.LoggedOut, *events.TemporaryBan:
  131. outputType = QRChannelErrUnexpectedEvent
  132. default:
  133. return
  134. }
  135. close(qrc.stopQRs)
  136. if atomic.CompareAndSwapUint32(&qrc.closed, 0, 1) {
  137. qrc.log.Debugf("Closing channel with status %+v", outputType)
  138. qrc.output <- outputType
  139. close(qrc.output)
  140. } else {
  141. qrc.log.Debugf("Got status %+v, but channel is already closed", outputType)
  142. }
  143. // Has to be done in background because otherwise there's a deadlock with eventHandlersLock
  144. go qrc.cli.RemoveEventHandler(qrc.handlerID)
  145. }
  146. // GetQRChannel returns a channel that automatically outputs a new QR code when the previous one expires.
  147. //
  148. // This must be called *before* Connect(). It will then listen to all the relevant events from the client.
  149. //
  150. // The last value to be emitted will be a special event like "success", "timeout" or another error code
  151. // depending on the result of the pairing. The channel will be closed immediately after one of those.
  152. func (cli *Client) GetQRChannel(ctx context.Context) (<-chan QRChannelItem, error) {
  153. if cli == nil {
  154. return nil, ErrClientIsNil
  155. } else if cli.IsConnected() {
  156. return nil, ErrQRAlreadyConnected
  157. } else if cli.Store.ID != nil {
  158. return nil, ErrQRStoreContainsID
  159. }
  160. ch := make(chan QRChannelItem, 8)
  161. qrc := qrChannel{
  162. output: ch,
  163. stopQRs: make(chan struct{}),
  164. cli: cli,
  165. log: cli.Log.Sub("QRChannel"),
  166. ctx: ctx,
  167. }
  168. qrc.handlerID = cli.AddEventHandler(qrc.handleEvent)
  169. return ch, nil
  170. }