diff --git a/client.go b/client.go index ca6c95a..c3a2802 100644 --- a/client.go +++ b/client.go @@ -21,6 +21,7 @@ const ( StateDisconnected ConnState = iota StateConnected StateSessionEstablished + StateStreamError ) // Event is a structure use to convey event changes related to client state. This @@ -28,6 +29,7 @@ const ( type Event struct { State ConnState Description string + StreamError string } // EventHandler is use to pass events about state of the connection to @@ -49,6 +51,13 @@ func (em EventManager) updateState(state ConnState) { } } +func (em EventManager) streamError(error, desc string) { + em.CurrentState = StateStreamError + if em.Handler != nil { + em.Handler(Event{State: em.CurrentState, StreamError: error, Description: desc}) + } +} + // Client // ============================================================================ @@ -164,8 +173,17 @@ func (c *Client) recv() (err error) { c.updateState(StateDisconnected) return err } + + // Handle stream errors + switch packet := val.(type) { + case StreamError: + c.RecvChannel <- val + close(c.RecvChannel) + c.streamError(packet.Error.Local, packet.Text) + return errors.New("stream error: " + packet.Error.Local) + } + c.RecvChannel <- val - val = nil } } diff --git a/client_manager.go b/client_manager.go index 662aad4..75599be 100644 --- a/client_manager.go +++ b/client_manager.go @@ -40,6 +40,12 @@ func (cm *ClientManager) Start() error { case StateDisconnected: // Reconnect on disconnection cm.connect() + case StateStreamError: + cm.Client.Disconnect() + // Only try reconnecting if we have not been kicked by another session to avoid connection loop. + if e.StreamError != "conflict" { + cm.connect() + } } } @@ -62,8 +68,6 @@ func (cm *ClientManager) connect() error { // TODO: Make it possible to define logger to log disconnect and reconnection attempts cm.Metrics = initMetrics() - // TODO: Test for non recoverable errors (invalid username and password) and return an error - // to start caller. We do not want to retry on non recoverable errors. if cm.Client.Session, err = cm.Client.Connect(); err != nil { var actualErr ConnError if xerrors.As(err, &actualErr) { @@ -72,7 +76,7 @@ func (cm *ClientManager) connect() error { } } backoff.Wait() - } else { + } else { // We are connected, we can leave the retry loop break } } diff --git a/stream.go b/stream.go index 1e0df97..a66ac85 100644 --- a/stream.go +++ b/stream.go @@ -37,6 +37,7 @@ func (streamFeatureDecoder) decode(p *xml.Decoder, se xml.StartElement) (StreamF type StreamError struct { XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` Error xml.Name `xml:",any"` + Text string `xml:"urn:ietf:params:xml:ns:xmpp-streams text"` } func (StreamError) Name() string {