feat: Don't attempt reconnections when the error is unrecoverable
Fixes #25. Should fix #24.
This commit is contained in:
parent
7f294d6632
commit
96d9ce4761
@ -373,7 +373,7 @@ class XmppConnection {
|
||||
/// Called when a stream ending error has occurred
|
||||
Future<void> handleError(XmppError error) async {
|
||||
_log.severe('handleError called with ${error.toString()}');
|
||||
|
||||
|
||||
// Whenever we encounter an error that would trigger a reconnection attempt while
|
||||
// the connection result is being awaited, don't attempt a reconnection but instead
|
||||
// try to gracefully disconnect.
|
||||
@ -390,11 +390,18 @@ class XmppConnection {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _connectivityManager.hasConnection()) {
|
||||
if (!error.isRecoverable()) {
|
||||
// We cannot recover this error
|
||||
_log.severe('Since a $error is not recoverable, not attempting a reconnection');
|
||||
await _setConnectionState(XmppConnectionState.error);
|
||||
} else {
|
||||
await _setConnectionState(XmppConnectionState.notConnected);
|
||||
await _sendEvent(
|
||||
NonRecoverableErrorEvent(error),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// The error is recoverable
|
||||
await _setConnectionState(XmppConnectionState.notConnected);
|
||||
await _reconnectionPolicy.onFailure();
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,37 @@
|
||||
import 'package:moxxmpp/src/socket.dart';
|
||||
|
||||
/// An internal error class
|
||||
abstract class XmppError {}
|
||||
// ignore: one_member_abstracts
|
||||
abstract class XmppError {
|
||||
/// Return true if we can recover from the error by attempting a reconnection.
|
||||
bool isRecoverable();
|
||||
}
|
||||
|
||||
/// Returned if we could not establish a TCP connection
|
||||
/// to the server.
|
||||
class NoConnectionError extends XmppError {}
|
||||
class NoConnectionError extends XmppError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if a socket error occured
|
||||
class SocketError extends XmppError {
|
||||
SocketError(this.event);
|
||||
final XmppSocketErrorEvent event;
|
||||
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if we time out
|
||||
class TimeoutError extends XmppError {}
|
||||
class TimeoutError extends XmppError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if we received a stream error
|
||||
class StreamError extends XmppError {}
|
||||
class StreamError extends XmppError {
|
||||
// TODO(PapaTutuWawa): Be more precise
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:moxxmpp/src/connection.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/roster/roster.dart';
|
||||
@ -236,3 +237,12 @@ class OmemoDeviceListUpdatedEvent extends XmppEvent {
|
||||
final JID jid;
|
||||
final List<int> deviceList;
|
||||
}
|
||||
|
||||
/// Triggered when a reconnection is not performed due to a non-recoverable
|
||||
/// error.
|
||||
class NonRecoverableErrorEvent extends XmppEvent {
|
||||
NonRecoverableErrorEvent(this.error);
|
||||
|
||||
/// The error in question.
|
||||
final XmppError error;
|
||||
}
|
||||
|
@ -8,12 +8,20 @@ import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class ResourceBindingFailedError extends NegotiatorError {}
|
||||
class ResourceBindingFailedError extends NegotiatorError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// A negotiator that implements resource binding against a random server-provided
|
||||
/// resource.
|
||||
class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
||||
ResourceBindingNegotiator() : super(0, false, bindXmlns, resourceBindingNegotiator);
|
||||
|
||||
ResourceBindingNegotiator() : _requestSent = false, super(0, false, bindXmlns, resourceBindingNegotiator);
|
||||
bool _requestSent;
|
||||
/// Flag indicating the state of the negotiator:
|
||||
/// - True: We sent a binding request
|
||||
/// - False: We have not yet sent the binding request
|
||||
bool _requestSent = false;
|
||||
|
||||
@override
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
|
@ -1,3 +1,50 @@
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
class SaslFailedError extends NegotiatorError {}
|
||||
abstract class SaslError extends NegotiatorError {
|
||||
static SaslError fromFailure(XMLNode failure) {
|
||||
XMLNode? error;
|
||||
for (final child in failure.children) {
|
||||
if (child.tag == 'text') continue;
|
||||
|
||||
error = child;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (error?.tag) {
|
||||
case 'credentials-expired': return SaslCredentialsExpiredError();
|
||||
case 'not-authorized': return SaslNotAuthorizedError();
|
||||
case 'account-disabled': return SaslAccountDisabledError();
|
||||
}
|
||||
|
||||
return SaslUnspecifiedError();
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <not-authorized /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-not-authorized).
|
||||
class SaslNotAuthorizedError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <credentials-expired /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-credentials-expired).
|
||||
class SaslCredentialsExpiredError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <account-disabled /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-account-disabled).
|
||||
class SaslAccountDisabledError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// An unspecified SASL error, i.e. everything not matched by any more precise erorr
|
||||
/// class.
|
||||
class SaslUnspecifiedError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
@ -59,7 +59,9 @@ class SaslPlainNegotiator extends SaslNegotiator {
|
||||
// We assume it's a <failure/>
|
||||
final error = nonza.children.first.tag;
|
||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||
return Result(SaslFailedError());
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,9 @@ class SaslScramNegotiator extends SaslNegotiator {
|
||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||
|
||||
_scramState = ScramState.error;
|
||||
return Result(SaslFailedError());
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
final challengeBase64 = nonza.innerText();
|
||||
@ -236,7 +238,9 @@ class SaslScramNegotiator extends SaslNegotiator {
|
||||
final error = nonza.children.first.tag;
|
||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||
_scramState = ScramState.error;
|
||||
return Result(SaslFailedError());
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: This assumes that the string is always "v=..." and contains no other parameters
|
||||
@ -246,13 +250,17 @@ class SaslScramNegotiator extends SaslNegotiator {
|
||||
//final error = nonza.children.first.tag;
|
||||
//attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||
_scramState = ScramState.error;
|
||||
return Result(SaslFailedError());
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
||||
return const Result(NegotiatorState.done);
|
||||
case ScramState.error:
|
||||
return Result(SaslFailedError());
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,10 @@ enum _StartTlsState {
|
||||
requested
|
||||
}
|
||||
|
||||
class StartTLSFailedError extends NegotiatorError {}
|
||||
class StartTLSFailedError extends NegotiatorError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
class StartTLSNonza extends XMLNode {
|
||||
StartTLSNonza() : super.xmlns(
|
||||
@ -19,15 +22,15 @@ class StartTLSNonza extends XMLNode {
|
||||
);
|
||||
}
|
||||
|
||||
/// A negotiator implementing StartTLS.
|
||||
class StartTlsNegotiator extends XmppFeatureNegotiatorBase {
|
||||
|
||||
StartTlsNegotiator()
|
||||
: _state = _StartTlsState.ready,
|
||||
_log = Logger('StartTlsNegotiator'),
|
||||
super(10, true, startTlsXmlns, startTlsNegotiator);
|
||||
_StartTlsState _state;
|
||||
StartTlsNegotiator() : super(10, true, startTlsXmlns, startTlsNegotiator);
|
||||
|
||||
final Logger _log;
|
||||
/// The state of the negotiator.
|
||||
_StartTlsState _state = _StartTlsState.ready;
|
||||
|
||||
/// Logger.
|
||||
final Logger _log = Logger('StartTlsNegotiator');
|
||||
|
||||
@override
|
||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
|
||||
|
Loading…
Reference in New Issue
Block a user