Parcourir la source

Add automatic connection retries

Tulir Asokan il y a 6 ans
Parent
commit
23747d4917
4 fichiers modifiés avec 73 ajouts et 14 suppressions
  1. 5 0
      commands.go
  2. 10 2
      config/bridge.go
  3. 1 0
      config/config.go
  4. 57 12
      user.go

+ 5 - 0
commands.go

@@ -182,6 +182,9 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
 			ce.Reply("You are not logged in.")
 			ce.Reply("You are not logged in.")
 			return
 			return
 		}
 		}
+	} else if err == whatsapp.ErrLoginInProgress {
+		ce.Reply("A login or reconnection is already in progress.")
+		return
 	}
 	}
 	if err != nil {
 	if err != nil {
 		ce.User.log.Warnln("Error while reconnecting:", err)
 		ce.User.log.Warnln("Error while reconnecting:", err)
@@ -190,6 +193,7 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
 				ce.Reply("You were already connected.")
 				ce.Reply("You were already connected.")
 			} else {
 			} else {
 				ce.User.Connected = true
 				ce.User.Connected = true
+				ce.User.ConnectionErrors = 0
 				ce.Reply("You were already connected, but the bridge hadn't noticed. Fixed that now.")
 				ce.Reply("You were already connected, but the bridge hadn't noticed. Fixed that now.")
 			}
 			}
 		} else if err.Error() == "restore session connection timed out" {
 		} else if err.Error() == "restore session connection timed out" {
@@ -200,6 +204,7 @@ func (handler *CommandHandler) CommandReconnect(ce *CommandEvent) {
 		return
 		return
 	}
 	}
 	ce.User.Connected = true
 	ce.User.Connected = true
+	ce.User.ConnectionErrors = 0
 	ce.Reply("Reconnected successfully.")
 	ce.Reply("Reconnected successfully.")
 }
 }
 
 

+ 10 - 2
config/bridge.go

@@ -33,7 +33,9 @@ type BridgeConfig struct {
 	UsernameTemplate    string `yaml:"username_template"`
 	UsernameTemplate    string `yaml:"username_template"`
 	DisplaynameTemplate string `yaml:"displayname_template"`
 	DisplaynameTemplate string `yaml:"displayname_template"`
 
 
-	ConnectionTimeout int `yaml:"connection_timeout"`
+	ConnectionTimeout     int  `yaml:"connection_timeout"`
+	MaxConnectionAttempts int  `yaml:"max_connection_attempts"`
+	ReportConnectionRetry bool `yaml:"report_connection_retry"`
 
 
 	CommandPrefix string `yaml:"command_prefix"`
 	CommandPrefix string `yaml:"command_prefix"`
 
 
@@ -43,6 +45,12 @@ type BridgeConfig struct {
 	displaynameTemplate *template.Template `yaml:"-"`
 	displaynameTemplate *template.Template `yaml:"-"`
 }
 }
 
 
+func (bc *BridgeConfig) setDefaults() {
+	bc.ConnectionTimeout = 20
+	bc.MaxConnectionAttempts = 3
+	bc.ReportConnectionRetry = true
+}
+
 type umBridgeConfig BridgeConfig
 type umBridgeConfig BridgeConfig
 
 
 func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
@@ -61,7 +69,7 @@ func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
 }
 }
 
 
 type UsernameTemplateArgs struct {
 type UsernameTemplateArgs struct {
-	UserID   string
+	UserID string
 }
 }
 
 
 func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8) {
 func (bc BridgeConfig) FormatDisplayname(contact whatsapp.Contact) (string, int8) {

+ 1 - 0
config/config.go

@@ -64,6 +64,7 @@ type Config struct {
 func (config *Config) setDefaults() {
 func (config *Config) setDefaults() {
 	config.AppService.Database.MaxOpenConns = 20
 	config.AppService.Database.MaxOpenConns = 20
 	config.AppService.Database.MaxIdleConns = 2
 	config.AppService.Database.MaxIdleConns = 2
+	config.Bridge.setDefaults()
 }
 }
 
 
 func Load(path string) (*Config, error) {
 func Load(path string) (*Config, error) {

+ 57 - 12
user.go

@@ -44,6 +44,8 @@ type User struct {
 	Admin       bool
 	Admin       bool
 	Whitelisted bool
 	Whitelisted bool
 	Connected   bool
 	Connected   bool
+
+	ConnectionErrors int
 }
 }
 
 
 func (bridge *Bridge) GetUserByMXID(userID types.MatrixUserID) *User {
 func (bridge *Bridge) GetUserByMXID(userID types.MatrixUserID) *User {
@@ -182,6 +184,7 @@ func (user *User) RestoreSession() bool {
 			return false
 			return false
 		}
 		}
 		user.Connected = true
 		user.Connected = true
+		user.ConnectionErrors = 0
 		user.SetSession(&sess)
 		user.SetSession(&sess)
 		user.log.Debugln("Session restored successfully")
 		user.log.Debugln("Session restored successfully")
 	}
 	}
@@ -236,6 +239,7 @@ func (user *User) Login(ce *CommandEvent) {
 		return
 		return
 	}
 	}
 	user.Connected = true
 	user.Connected = true
+	user.ConnectionErrors = 0
 	user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1)
 	user.JID = strings.Replace(user.Conn.Info.Wid, whatsappExt.OldUserSuffix, whatsappExt.NewUserSuffix, 1)
 	user.SetSession(&session)
 	user.SetSession(&session)
 	ce.Reply("Successfully logged in. Now, you may ask for `sync [--create]`.")
 	ce.Reply("Successfully logged in. Now, you may ask for `sync [--create]`.")
@@ -243,22 +247,63 @@ func (user *User) Login(ce *CommandEvent) {
 
 
 func (user *User) HandleError(err error) {
 func (user *User) HandleError(err error) {
 	user.log.Errorln("WhatsApp error:", err)
 	user.log.Errorln("WhatsApp error:", err)
-	closed, ok := err.(*whatsapp.ErrConnectionClosed)
-	if ok {
-		if closed.Code != 1000 {
-			msg := fmt.Sprintf("\u26a0 Your WhatsApp connection was closed with websocket status code %d.\n\n"+
-				"Use the `reconnect` command to reconnect.", closed.Code)
-			_, _ = user.bridge.Bot.SendMessageEvent(user.ManagementRoom, mautrix.EventMessage, format.RenderMarkdown(msg))
+	var msg string
+	if closed, ok := err.(*whatsapp.ErrConnectionClosed); ok {
+		user.Connected = false
+		if closed.Code == 1000 {
+			// Normal closure
+			return
 		}
 		}
+		user.ConnectionErrors++
+		msg = fmt.Sprintf("Your WhatsApp connection was closed with websocket status code %d", closed.Code)
+	} else if failed, ok := err.(*whatsapp.ErrConnectionFailed); ok {
 		user.Connected = false
 		user.Connected = false
+		user.ConnectionErrors++
+		msg = fmt.Sprintf("Your WhatsApp connection failed: %v", failed.Err)
+	} else {
+		// Unknown error, probably mostly harmless
+		return
 	}
 	}
-	failed, ok := err.(*whatsapp.ErrConnectionFailed)
-	if ok {
-		msg := fmt.Sprintf("\u26a0 Your WhatsApp connection failed: %v.\n\n"+
-			"Use the `reconnect` command to reconnect.", failed.Err)
-		_, _ = user.bridge.Bot.SendMessageEvent(user.ManagementRoom, mautrix.EventMessage, format.RenderMarkdown(msg))
-		user.Connected = false
+	if user.ConnectionErrors > user.bridge.Config.Bridge.MaxConnectionAttempts {
+		content := format.RenderMarkdown(fmt.Sprintf("%s. Use the `reconnect` command to reconnect.", msg))
+		_, _ = user.bridge.Bot.SendMessageEvent(user.ManagementRoom, mautrix.EventMessage, content)
+		return
 	}
 	}
+	if user.bridge.Config.Bridge.ReportConnectionRetry {
+		_, _ = user.bridge.Bot.SendNotice(user.ManagementRoom, fmt.Sprintf("%s. Reconnecting...", msg))
+		// Don't want the same error to be repeated
+		msg = ""
+	}
+	tries := 0
+	for user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
+		err = user.Conn.Restore()
+		if err == nil {
+			user.ConnectionErrors = 0
+			user.Connected = true
+			_, _ = user.bridge.Bot.SendNotice(user.ManagementRoom, "Reconnected successfully")
+			return
+		}
+		user.log.Errorln("Error while trying to reconnect after disconnection:", err)
+		tries++
+		user.ConnectionErrors++
+		if user.ConnectionErrors <= user.bridge.Config.Bridge.MaxConnectionAttempts {
+			if user.bridge.Config.Bridge.ReportConnectionRetry {
+				_, _ = user.bridge.Bot.SendNotice(user.ManagementRoom,
+					fmt.Sprintf("Reconnection attempt failed: %v. Retrying in 10 seconds...", err))
+			}
+			time.Sleep(10 * time.Second)
+		}
+	}
+
+	if user.bridge.Config.Bridge.ReportConnectionRetry {
+		msg = fmt.Sprintf("%d reconnection attempts failed. Use the `reconnect` command to try to reconnect manually.", tries)
+	} else {
+		msg = fmt.Sprintf("\u26a0 %sAdditionally, %d reconnection attempts failed. "+
+			"Use the `reconnect` command to try to reconnect.", msg, tries)
+	}
+
+	content := format.RenderMarkdown(msg)
+	_, _ = user.bridge.Bot.SendMessageEvent(user.ManagementRoom, mautrix.EventMessage, content)
 }
 }
 
 
 func (user *User) HandleJSONParseError(err error) {
 func (user *User) HandleJSONParseError(err error) {