Add better bridge states

This commit is contained in:
Tulir Asokan 2023-07-15 15:02:03 +03:00
parent 7860fb64c8
commit bbc4da21b7
9 changed files with 100 additions and 64 deletions

View file

@ -26,10 +26,20 @@ const (
GMNotConnected status.BridgeStateErrorCode = "gm-not-connected"
GMConnecting status.BridgeStateErrorCode = "gm-connecting"
GMConnectionFailed status.BridgeStateErrorCode = "gm-connection-failed"
GMBrowserInactive status.BridgeStateErrorCode = "gm-browser-inactive"
GMBrowserInactiveTimeout status.BridgeStateErrorCode = "gm-browser-inactive-timeout"
GMBrowserInactiveInactivity status.BridgeStateErrorCode = "gm-browser-inactive-inactivity"
)
func init() {
status.BridgeStateHumanErrors.Update(status.BridgeStateErrorMap{})
status.BridgeStateHumanErrors.Update(status.BridgeStateErrorMap{
GMListenError: "Error polling messages from Google Messages server, the bridge will try to reconnect",
GMFatalError: "Google Messages login was invalidated, please re-link the bridge",
GMBrowserInactive: "Google Messages opened in another browser",
GMBrowserInactiveTimeout: "Google Messages disconnected due to timeout",
GMBrowserInactiveInactivity: "Google Messages disconnected due to inactivity",
})
}
func (user *User) GetRemoteID() string {

View file

@ -148,9 +148,7 @@ func fnDeleteSession(ce *WrappedCommandEvent) {
ce.Reply("Nothing to purge: no session information stored and no active connection.")
return
}
ce.User.removeFromPhoneMap(status.BridgeState{StateEvent: status.StateLoggedOut})
ce.User.DeleteConnection()
ce.User.DeleteSession()
ce.User.Logout(status.BridgeState{StateEvent: status.StateLoggedOut})
ce.Reply("Session information purged")
}

2
go.mod
View file

@ -9,7 +9,7 @@ require (
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
go.mau.fi/mautrix-gmessages/libgm v0.1.0
maunium.net/go/maulogger/v2 v2.4.1
maunium.net/go/mautrix v0.15.4-0.20230711231757-65db706cd3ce
maunium.net/go/mautrix v0.15.4-0.20230714233218-82f817eff669
)
require (

4
go.sum
View file

@ -81,5 +81,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.15.4-0.20230711231757-65db706cd3ce h1:SYYPKkcJLI012g+p3jZ/Qnqm5VQd7kFZSSEHOtI4NZA=
maunium.net/go/mautrix v0.15.4-0.20230711231757-65db706cd3ce/go.mod h1:zLrQqdxJlLkurRCozTc9CL6FySkgZlO/kpCYxBILSLE=
maunium.net/go/mautrix v0.15.4-0.20230714233218-82f817eff669 h1:/7+suCGeh70hK0E7P3/GZLJ+8sMAC82ZBWYDoKSbpOE=
maunium.net/go/mautrix v0.15.4-0.20230714233218-82f817eff669/go.mod h1:zLrQqdxJlLkurRCozTc9CL6FySkgZlO/kpCYxBILSLE=

View file

@ -1,6 +1,7 @@
package events
import (
"fmt"
"net/http"
"go.mau.fi/mautrix-gmessages/libgm/binary"
@ -28,8 +29,17 @@ func NewAuthTokenRefreshed(token []byte) *AuthTokenRefreshed {
}
}
type HTTPError struct {
Action string
Resp *http.Response
}
func (he HTTPError) Error() string {
return fmt.Sprintf("http %d while %s", he.Resp.StatusCode, he.Action)
}
type ListenFatalError struct {
Resp *http.Response
Error error
}
type ListenTemporaryError struct {

View file

@ -9,27 +9,3 @@ func NewBrowserActive(sessionId string) *BrowserActive {
SessionId: sessionId,
}
}
type MOBILE_BATTERY_RESTORED struct{}
func NewMobileBatteryRestored() *MOBILE_BATTERY_RESTORED {
return &MOBILE_BATTERY_RESTORED{}
}
type MOBILE_BATTERY_LOW struct{}
func NewMobileBatteryLow() *MOBILE_BATTERY_LOW {
return &MOBILE_BATTERY_LOW{}
}
type MOBILE_DATA_CONNECTION struct{}
func NewMobileDataConnection() *MOBILE_DATA_CONNECTION {
return &MOBILE_DATA_CONNECTION{}
}
type MOBILE_WIFI_CONNECTION struct{}
func NewMobileWifiConnection() *MOBILE_WIFI_CONNECTION {
return &MOBILE_WIFI_CONNECTION{}
}

View file

@ -41,6 +41,7 @@ func (r *RPC) ListenReceiveMessages(payload []byte) {
err := r.client.refreshAuthToken()
if err != nil {
r.client.Logger.Err(err).Msg("Error refreshing auth token")
r.client.triggerEvent(&events.ListenFatalError{Error: fmt.Errorf("failed to refresh auth token: %w", err)})
return
}
}
@ -59,12 +60,12 @@ func (r *RPC) ListenReceiveMessages(payload []byte) {
time.Sleep(5 * time.Second)
continue
}
if resp.StatusCode >= 400 && resp.StatusCode < 501 {
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
r.client.Logger.Error().Int("status_code", resp.StatusCode).Msg("Error making listen request")
r.client.triggerEvent(&events.ListenFatalError{Resp: resp})
r.client.triggerEvent(&events.ListenFatalError{Error: events.HTTPError{Action: "polling", Resp: resp}})
return
} else if resp.StatusCode >= 500 {
r.client.triggerEvent(&events.ListenTemporaryError{Error: fmt.Errorf("http %d while polling", resp.StatusCode)})
r.client.triggerEvent(&events.ListenTemporaryError{Error: events.HTTPError{Action: "polling", Resp: resp}})
errored = true
r.client.Logger.Debug().Int("statusCode", resp.StatusCode).Msg("5xx error in long polling, retrying in 5 seconds")
time.Sleep(5 * time.Second)

View file

@ -34,27 +34,7 @@ func (c *Client) handleUserAlertEvent(res *pblite.Response, data *binary.UserAle
} else {
go c.handleClientReady(newSessionId)
}
case binary.AlertType_MOBILE_BATTERY_LOW:
c.Logger.Info().Msg("[MOBILE_BATTERY_LOW] Mobile device is on low battery")
evt := events.NewMobileBatteryLow()
c.triggerEvent(evt)
case binary.AlertType_MOBILE_BATTERY_RESTORED:
c.Logger.Info().Msg("[MOBILE_BATTERY_RESTORED] Mobile device has restored enough battery!")
evt := events.NewMobileBatteryRestored()
c.triggerEvent(evt)
case binary.AlertType_MOBILE_DATA_CONNECTION:
c.Logger.Info().Msg("[MOBILE_DATA_CONNECTION] Mobile device is now using data connection")
evt := events.NewMobileDataConnection()
c.triggerEvent(evt)
case binary.AlertType_MOBILE_WIFI_CONNECTION:
c.Logger.Info().Msg("[MOBILE_WIFI_CONNECTION] Mobile device is now using wifi connection")
evt := events.NewMobileWifiConnection()
c.triggerEvent(evt)
default:
c.Logger.Info().Any("data", data).Any("res", res).Msg("Got unknown alert type")
c.triggerEvent(data)
}
}

77
user.go
View file

@ -67,6 +67,11 @@ type User struct {
spaceMembershipChecked bool
longPollingError error
browserInactiveType status.BridgeStateErrorCode
batteryLow bool
mobileData bool
DoublePuppetIntent *appservice.IntentAPI
hackyLoginCommand *WrappedCommandEvent
@ -514,21 +519,21 @@ func (user *User) HandleEvent(event interface{}) {
user.hackyLoginCommandPrevEvent = user.sendQR(user.hackyLoginCommand, v.URL, user.hackyLoginCommandPrevEvent)
}
case *events.ListenFatalError:
user.BridgeState.Send(status.BridgeState{
StateEvent: status.StateUnknownError,
user.Logout(status.BridgeState{
StateEvent: status.StateBadCredentials,
Error: GMFatalError,
Message: fmt.Sprintf("HTTP %d in long polling loop", v.Resp.StatusCode),
Info: map[string]any{"go_error": v.Error.Error()},
})
case *events.ListenTemporaryError:
user.longPollingError = v.Error
user.BridgeState.Send(status.BridgeState{
StateEvent: status.StateTransientDisconnect,
Error: GMListenError,
Message: v.Error.Error(),
Info: map[string]any{"go_error": v.Error.Error()},
})
case *events.ListenRecovered:
user.BridgeState.Send(status.BridgeState{
StateEvent: status.StateConnected,
})
user.longPollingError = nil
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
case *events.PairSuccessful:
user.hackyLoginCommand = nil
user.hackyLoginCommandPrevEvent = ""
@ -550,7 +555,8 @@ func (user *User) HandleEvent(event interface{}) {
portal := user.GetPortalByID(v.GetConversationID())
portal.messages <- PortalMessage{evt: v, source: user}
case *events.ClientReady:
user.zlog.Trace().Any("data", v).Msg("Client is ready!")
user.browserInactiveType = ""
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
go func() {
for _, conv := range v.Conversations {
user.syncConversation(conv)
@ -558,11 +564,66 @@ func (user *User) HandleEvent(event interface{}) {
}()
case *events.BrowserActive:
user.zlog.Trace().Any("data", v).Msg("Browser active")
user.browserInactiveType = ""
case *binary.UserAlertEvent:
user.handleUserAlert(v)
default:
user.zlog.Trace().Any("data", v).Type("data_type", v).Msg("Unknown event")
}
}
func (user *User) handleUserAlert(v *binary.UserAlertEvent) {
user.zlog.Debug().Any("data", v).Msg("Got user alert event")
switch v.GetAlertType() {
case binary.AlertType_BROWSER_INACTIVE:
// TODO aggressively reactivate if configured to do so
user.browserInactiveType = GMBrowserInactive
case binary.AlertType_BROWSER_INACTIVE_FROM_TIMEOUT:
user.browserInactiveType = GMBrowserInactiveTimeout
case binary.AlertType_BROWSER_INACTIVE_FROM_INACTIVITY:
user.browserInactiveType = GMBrowserInactiveInactivity
case binary.AlertType_MOBILE_DATA_CONNECTION:
user.mobileData = true
case binary.AlertType_MOBILE_WIFI_CONNECTION:
user.mobileData = false
case binary.AlertType_MOBILE_BATTERY_LOW:
user.batteryLow = true
case binary.AlertType_MOBILE_BATTERY_RESTORED:
user.batteryLow = false
default:
return
}
user.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
}
func (user *User) FillBridgeState(state status.BridgeState) status.BridgeState {
if state.Info == nil {
state.Info = make(map[string]any)
}
state.Info["battery_low"] = user.batteryLow
state.Info["mobile_data"] = user.mobileData
state.Info["browser_active"] = user.browserInactiveType == ""
if state.StateEvent == status.StateConnected {
if user.longPollingError != nil {
state.StateEvent = status.StateTransientDisconnect
state.Error = GMListenError
state.Info["go_error"] = user.longPollingError.Error()
}
if user.browserInactiveType != "" {
state.StateEvent = status.StateTransientDisconnect
state.Error = user.browserInactiveType
}
}
return state
}
func (user *User) Logout(state status.BridgeState) {
user.removeFromPhoneMap(state)
// TODO invalidate token
user.DeleteConnection()
user.DeleteSession()
}
func (user *User) syncConversation(v *binary.Conversation) {
updateType := v.GetStatus()
portal := user.GetPortalByID(v.GetConversationID())