connectionevents.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright (c) 2021 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. "time"
  10. waBinary "go.mau.fi/whatsmeow/binary"
  11. "go.mau.fi/whatsmeow/store"
  12. "go.mau.fi/whatsmeow/types"
  13. "go.mau.fi/whatsmeow/types/events"
  14. )
  15. func (cli *Client) handleStreamError(ctx context.Context, node *waBinary.Node) {
  16. cli.isLoggedIn.Store(false)
  17. cli.clearResponseWaiters(node)
  18. code, _ := node.Attrs["code"].(string)
  19. conflict, _ := node.GetOptionalChildByTag("conflict")
  20. conflictType := conflict.AttrGetter().OptionalString("type")
  21. switch {
  22. case code == "515":
  23. if cli.DisableLoginAutoReconnect {
  24. cli.Log.Infof("Got 515 code, but login autoreconnect is disabled, not reconnecting")
  25. cli.dispatchEvent(&events.ManualLoginReconnect{})
  26. return
  27. }
  28. cli.Log.Infof("Got 515 code, reconnecting...")
  29. go func() {
  30. cli.Disconnect()
  31. err := cli.connect(ctx)
  32. if err != nil {
  33. cli.Log.Errorf("Failed to reconnect after 515 code: %v", err)
  34. }
  35. }()
  36. case code == "401" && conflictType == "device_removed":
  37. cli.expectDisconnect()
  38. cli.Log.Infof("Got device removed stream error, sending LoggedOut event and deleting session")
  39. go cli.dispatchEvent(&events.LoggedOut{OnConnect: false, Reason: events.ConnectFailureLoggedOut})
  40. err := cli.Store.Delete(ctx)
  41. if err != nil {
  42. cli.Log.Warnf("Failed to delete store after device_removed error: %v", err)
  43. }
  44. case conflictType == "replaced":
  45. cli.expectDisconnect()
  46. cli.Log.Infof("Got replaced stream error, sending StreamReplaced event")
  47. go cli.dispatchEvent(&events.StreamReplaced{})
  48. case code == "503":
  49. // This seems to happen when the server wants to restart or something.
  50. // The disconnection will be emitted as an events.Disconnected and then the auto-reconnect will do its thing.
  51. cli.Log.Warnf("Got 503 stream error, assuming automatic reconnect will handle it")
  52. case cli.RefreshCAT != nil && (code == events.ConnectFailureCATInvalid.NumberString() || code == events.ConnectFailureCATExpired.NumberString()):
  53. cli.Log.Infof("Got %s stream error, refreshing CAT before reconnecting...", code)
  54. cli.socketLock.RLock()
  55. defer cli.socketLock.RUnlock()
  56. err := cli.RefreshCAT(ctx)
  57. if err != nil {
  58. cli.Log.Errorf("Failed to refresh CAT: %v", err)
  59. cli.expectDisconnect()
  60. go cli.dispatchEvent(&events.CATRefreshError{Error: err})
  61. }
  62. default:
  63. cli.Log.Errorf("Unknown stream error: %s", node.XMLString())
  64. go cli.dispatchEvent(&events.StreamError{Code: code, Raw: node})
  65. }
  66. }
  67. func (cli *Client) handleIB(ctx context.Context, node *waBinary.Node) {
  68. children := node.GetChildren()
  69. for _, child := range children {
  70. ag := child.AttrGetter()
  71. switch child.Tag {
  72. case "downgrade_webclient":
  73. go cli.dispatchEvent(&events.QRScannedWithoutMultidevice{})
  74. case "offline_preview":
  75. cli.dispatchEvent(&events.OfflineSyncPreview{
  76. Total: ag.Int("count"),
  77. AppDataChanges: ag.Int("appdata"),
  78. Messages: ag.Int("message"),
  79. Notifications: ag.Int("notification"),
  80. Receipts: ag.Int("receipt"),
  81. })
  82. case "offline":
  83. cli.dispatchEvent(&events.OfflineSyncCompleted{
  84. Count: ag.Int("count"),
  85. })
  86. case "dirty":
  87. //ts := ag.UnixTime("timestamp")
  88. //typ := ag.String("type") // account_sync
  89. //go func() {
  90. // err := cli.MarkNotDirty(ctx, typ, ts)
  91. // zerolog.Ctx(ctx).Debug().Err(err).Msg("Marked dirty item as clean")
  92. //}()
  93. }
  94. }
  95. }
  96. func (cli *Client) handleConnectFailure(ctx context.Context, node *waBinary.Node) {
  97. ag := node.AttrGetter()
  98. reason := events.ConnectFailureReason(ag.Int("reason"))
  99. message := ag.OptionalString("message")
  100. willAutoReconnect := true
  101. switch {
  102. default:
  103. // By default, expect a disconnect (i.e. prevent auto-reconnect)
  104. cli.expectDisconnect()
  105. willAutoReconnect = false
  106. case reason == events.ConnectFailureServiceUnavailable || reason == events.ConnectFailureInternalServerError:
  107. // Auto-reconnect for 503s
  108. case reason == events.ConnectFailureCATInvalid || reason == events.ConnectFailureCATExpired:
  109. // Auto-reconnect when rotating CAT, lock socket to ensure refresh goes through before reconnect
  110. cli.socketLock.RLock()
  111. defer cli.socketLock.RUnlock()
  112. }
  113. if reason == 403 {
  114. cli.Log.Debugf(
  115. "Message for 403 connect failure: %s / %s",
  116. ag.OptionalString("logout_message_header"),
  117. ag.OptionalString("logout_message_subtext"),
  118. )
  119. }
  120. if reason.IsLoggedOut() {
  121. cli.Log.Infof("Got %s connect failure, sending LoggedOut event and deleting session", reason)
  122. go cli.dispatchEvent(&events.LoggedOut{OnConnect: true, Reason: reason})
  123. err := cli.Store.Delete(ctx)
  124. if err != nil {
  125. cli.Log.Warnf("Failed to delete store after %d failure: %v", int(reason), err)
  126. }
  127. } else if reason == events.ConnectFailureTempBanned {
  128. cli.Log.Warnf("Temporary ban connect failure: %s", node.XMLString())
  129. go cli.dispatchEvent(&events.TemporaryBan{
  130. Code: events.TempBanReason(ag.Int("code")),
  131. Expire: time.Duration(ag.Int("expire")) * time.Second,
  132. })
  133. } else if reason == events.ConnectFailureClientOutdated {
  134. cli.Log.Errorf("Client outdated (405) connect failure (client version: %s)", store.GetWAVersion().String())
  135. go cli.dispatchEvent(&events.ClientOutdated{})
  136. } else if reason == events.ConnectFailureCATInvalid || reason == events.ConnectFailureCATExpired {
  137. cli.Log.Infof("Got %d/%s connect failure, refreshing CAT before reconnecting...", int(reason), message)
  138. err := cli.RefreshCAT(ctx)
  139. if err != nil {
  140. cli.Log.Errorf("Failed to refresh CAT: %v", err)
  141. cli.expectDisconnect()
  142. go cli.dispatchEvent(&events.CATRefreshError{Error: err})
  143. }
  144. } else if willAutoReconnect {
  145. cli.Log.Warnf("Got %d/%s connect failure, assuming automatic reconnect will handle it", int(reason), message)
  146. } else {
  147. cli.Log.Warnf("Unknown connect failure: %s", node.XMLString())
  148. go cli.dispatchEvent(&events.ConnectFailure{Reason: reason, Message: message, Raw: node})
  149. }
  150. }
  151. func (cli *Client) handleConnectSuccess(ctx context.Context, node *waBinary.Node) {
  152. cli.Log.Infof("Successfully authenticated")
  153. cli.LastSuccessfulConnect = time.Now()
  154. cli.AutoReconnectErrors = 0
  155. cli.isLoggedIn.Store(true)
  156. nodeLID := node.AttrGetter().JID("lid")
  157. if cli.Store.Mobile {
  158. if err := cli.Store.Save(ctx); err != nil {
  159. cli.Log.Infof("Successfully authenticated")
  160. cli.Log.Errorf("Failed to save device store: %v", err)
  161. return
  162. }
  163. }
  164. if !cli.Store.LID.IsEmpty() && !nodeLID.IsEmpty() && cli.Store.LID != nodeLID {
  165. // This should probably never happen, but check just in case.
  166. cli.Log.Warnf("Stored LID doesn't match one in connect success: %s != %s", cli.Store.LID, nodeLID)
  167. cli.Store.LID = types.EmptyJID
  168. }
  169. if cli.Store.LID.IsEmpty() && !nodeLID.IsEmpty() {
  170. cli.Store.LID = nodeLID
  171. err := cli.Store.Save(ctx)
  172. if err != nil {
  173. cli.Log.Warnf("Failed to save device after updating LID: %v", err)
  174. } else {
  175. cli.Log.Infof("Updated LID to %s", cli.Store.LID)
  176. }
  177. }
  178. // Some users are missing their own LID-PN mapping even though it's already in the device table,
  179. // so do this unconditionally for a few months to ensure everyone gets the row.
  180. cli.StoreLIDPNMapping(ctx, cli.Store.GetLID(), cli.Store.GetJID())
  181. go func() {
  182. if dbCount, err := cli.Store.PreKeys.UploadedPreKeyCount(ctx); err != nil {
  183. cli.Log.Errorf("Failed to get number of prekeys in database: %v", err)
  184. } else if serverCount, err := cli.getServerPreKeyCount(ctx); err != nil {
  185. cli.Log.Warnf("Failed to get number of prekeys on server: %v", err)
  186. } else {
  187. cli.Log.Debugf("Database has %d prekeys, server says we have %d", dbCount, serverCount)
  188. if serverCount < MinPreKeyCount || dbCount < MinPreKeyCount {
  189. cli.uploadPreKeys(ctx, dbCount == 0 && serverCount == 0)
  190. sc, _ := cli.getServerPreKeyCount(ctx)
  191. cli.Log.Debugf("Prekey count after upload: %d", sc)
  192. }
  193. }
  194. err := cli.SetPassive(ctx, false)
  195. if err != nil {
  196. cli.Log.Warnf("Failed to send post-connect passive IQ: %v", err)
  197. }
  198. cli.dispatchEvent(&events.Connected{})
  199. cli.closeSocketWaitChan()
  200. }()
  201. }
  202. // SetPassive tells the WhatsApp server whether this device is passive or not.
  203. //
  204. // This seems to mostly affect whether the device receives certain events.
  205. // By default, whatsmeow will automatically do SetPassive(false) after connecting.
  206. func (cli *Client) SetPassive(ctx context.Context, passive bool) error {
  207. tag := "active"
  208. if passive {
  209. tag = "passive"
  210. }
  211. _, err := cli.sendIQ(ctx, infoQuery{
  212. Namespace: "passive",
  213. Type: "set",
  214. To: types.ServerJID,
  215. Content: []waBinary.Node{{Tag: tag}},
  216. })
  217. if err != nil {
  218. return err
  219. }
  220. return nil
  221. }