diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 7aa731d..2cf828f 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -24,6 +24,7 @@ import 'package:moxxmpp/src/settings.dart'; import 'package:moxxmpp/src/socket.dart'; import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stringxml.dart'; +import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; @@ -90,6 +91,32 @@ class XmppConnectionResult { final XmppError? error; } +/// The reason a call to [XmppConnection.connect] failed. +abstract class XmppConnectionError {} + +/// Returned by [XmppConnection.connect] when a connection is already active. +class ConnectionAlreadyRunningError extends XmppConnectionError {} + +/// Returned by [XmppConnection.connect] when a negotiator returned an unrecoverable +/// error. Only returned when waitUntilLogin is true. +class NegotiatorReturnedError extends XmppConnectionError { + NegotiatorReturnedError(this.error); + + /// The error returned by the negotiator. + final NegotiatorError error; +} + +class StreamFailureError extends XmppConnectionError { + StreamFailureError(this.error); + + /// The error that causes a connection failure. + final XmppError error; +} + +/// Returned by [XmppConnection.connect] when no connection could +/// be established. +class NoConnectionPossibleError extends XmppConnectionError {} + /// This class is a connection to the server. class XmppConnection { XmppConnection( @@ -180,7 +207,7 @@ class XmppConnection { /// Completers for certain actions // ignore: use_late_for_private_fields_and_variables - Completer? _connectionCompleter; + Completer>? _connectionCompleter; /// Negotiators final Map _featureNegotiators = {}; @@ -198,6 +225,9 @@ class XmppConnection { bool _isConnectionRunning = false; final Lock _connectionRunningLock = Lock(); + /// Flag indicating whether reconnection should be enabled after a successful connection. + bool _enableReconnectOnSuccess = false; + /// Enters the critical section for accessing [XmppConnection._isConnectionRunning] /// and does the following: /// - if _isConnectionRunning is false, set it to true and return false. @@ -404,9 +434,10 @@ class XmppConnection { state: XmppConnectionState.error, ); _connectionCompleter?.complete( - XmppConnectionResult( - false, - error: error, + Result( + StreamFailureError( + error, + ), ), ); _connectionCompleter = null; @@ -859,8 +890,13 @@ class XmppConnection { await _resetIsConnectionRunning(); await _setConnectionState(XmppConnectionState.connected); + // Enable reconnections + if (_enableReconnectOnSuccess) { + await _reconnectionPolicy.setShouldReconnect(true); + } + // Resolve the connection completion future - _connectionCompleter?.complete(const XmppConnectionResult(true)); + _connectionCompleter?.complete(const Result(true)); _connectionCompleter = null; // Tell consumers of the event stream that we're done with stream feature @@ -1112,45 +1148,26 @@ class XmppConnection { ); } - /// Like [connect] but the Future resolves when the resource binding is either done or - /// SASL has failed. - Future connectAwaitable({ - String? lastResource, - bool waitForConnection = false, - }) async { - _runPreConnectionAssertions(); - await _resetIsConnectionRunning(); - _connectionCompleter = Completer(); - _log.finest('Calling connect() from connectAwaitable'); - await connect( - lastResource: lastResource, - waitForConnection: waitForConnection, - shouldReconnect: false, - ); - return _connectionCompleter!.future; - } - - /// Start the connection process using the provided connection settings. - Future connect({ + Future> _connectImpl({ String? lastResource, bool waitForConnection = false, bool shouldReconnect = true, + bool waitUntilLogin = false, + bool enableReconnectOnSuccess = true, }) async { - if (_connectionState != XmppConnectionState.notConnected && - _connectionState != XmppConnectionState.error) { - _log.fine( - 'Cancelling this connection attempt as one appears to be already running.', - ); - return; - } - _runPreConnectionAssertions(); await _resetIsConnectionRunning(); + if (waitUntilLogin) { + _log.finest('Setting up completer for awaiting completed login'); + _connectionCompleter = Completer(); + } + if (lastResource != null) { setResource(lastResource); } + _enableReconnectOnSuccess = enableReconnectOnSuccess; if (shouldReconnect) { await _reconnectionPolicy.setShouldReconnect(true); } @@ -1182,6 +1199,8 @@ class XmppConnection { ); if (!result) { await handleError(NoConnectionError()); + + return Result(NoConnectionPossibleError()); } else { await _reconnectionPolicy.onSuccess(); _log.fine('Preparing the internal state for a connection attempt'); @@ -1190,6 +1209,66 @@ class XmppConnection { _updateRoutingState(RoutingState.negotiating); _isAuthenticated = false; _sendStreamHeader(); + + if (waitUntilLogin) { + return _connectionCompleter!.future; + } else { + return const Result(true); + } + } + } + + /// Start the connection process using the provided connection settings. + /// + /// If [lastResource] is set, then its value is used as the connection's resource. + /// Useful for stream resumption. + /// + /// [shouldReconnect] indicates whether the reconnection attempts should be + /// automatically performed after a fatal failure of any kind occurs. + /// + /// [waitForConnection] indicates whether the connection should wait for the "go" + /// signal from a registered connectivity manager. + /// + /// If [waitUntilLogin] is set to true, the future will resolve when either + /// the connection has been successfully established (authentication included) or + /// a failure occured. If set to false, then the future will immediately resolve + /// to false. + /// + /// [enableReconnectOnSuccess] indicates that automatic reconnection is to be + /// enabled once the connection has been successfully established. + Future> connect({ + String? lastResource, + bool? shouldReconnect, + bool waitForConnection = false, + bool waitUntilLogin = false, + bool enableReconnectOnSuccess = true, + }) async { + if (_connectionState != XmppConnectionState.notConnected && + _connectionState != XmppConnectionState.error) { + _log.fine( + 'Cancelling this connection attempt as one appears to be already running.', + ); + return Future.value( + Result( + ConnectionAlreadyRunningError(), + ), + ); + } + + final result = _connectImpl( + lastResource: lastResource, + shouldReconnect: shouldReconnect ?? !waitUntilLogin, + waitForConnection: waitForConnection, + enableReconnectOnSuccess: enableReconnectOnSuccess, + ); + if (waitUntilLogin) { + return result; + } else { + return Future.value( + const Result( + false, + ), + ); } } }