Compare commits
	
		
			5 Commits
		
	
	
		
			1cc266c675
			...
			7e588f01b0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7e588f01b0 | |||
| c7c6c9dae4 | |||
| c77cfc4dcd | |||
| 1bd61076ea | |||
| bff4a6f707 | 
| @ -1,6 +1,7 @@ | ||||
| library moxxmpp; | ||||
| 
 | ||||
| export 'package:moxxmpp/src/connection.dart'; | ||||
| export 'package:moxxmpp/src/connectivity.dart'; | ||||
| export 'package:moxxmpp/src/errors.dart'; | ||||
| export 'package:moxxmpp/src/events.dart'; | ||||
| export 'package:moxxmpp/src/iq.dart'; | ||||
|  | ||||
| @ -4,6 +4,7 @@ import 'package:meta/meta.dart'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxmpp/src/awaiter.dart'; | ||||
| import 'package:moxxmpp/src/buffer.dart'; | ||||
| import 'package:moxxmpp/src/connectivity.dart'; | ||||
| import 'package:moxxmpp/src/errors.dart'; | ||||
| import 'package:moxxmpp/src/events.dart'; | ||||
| import 'package:moxxmpp/src/iq.dart'; | ||||
| @ -94,12 +95,14 @@ class XmppConnectionResult { | ||||
| class XmppConnection { | ||||
|   XmppConnection( | ||||
|     ReconnectionPolicy reconnectionPolicy, | ||||
|     ConnectivityManager connectivityManager, | ||||
|     this._socket, | ||||
|     { | ||||
|       this.connectionPingDuration = const Duration(minutes: 3), | ||||
|       this.connectingTimeout = const Duration(minutes: 2), | ||||
|     } | ||||
|   ) : _reconnectionPolicy = reconnectionPolicy { | ||||
|   ) : _reconnectionPolicy = reconnectionPolicy, | ||||
|       _connectivityManager = connectivityManager { | ||||
|     // Allow the reconnection policy to perform reconnections by itself | ||||
|     _reconnectionPolicy.register( | ||||
|       _attemptReconnection, | ||||
| @ -122,13 +125,16 @@ class XmppConnection { | ||||
|   /// The data stream of the socket | ||||
|   late final Stream<String> _socketStream; | ||||
| 
 | ||||
| 
 | ||||
|   /// Connection settings | ||||
|   late ConnectionSettings _connectionSettings; | ||||
| 
 | ||||
|   /// A policy on how to reconnect  | ||||
|   final ReconnectionPolicy _reconnectionPolicy; | ||||
| 
 | ||||
|   /// The class responsible for preventing errors on initial connection due | ||||
|   /// to no network. | ||||
|   final ConnectivityManager _connectivityManager; | ||||
| 
 | ||||
|   /// A helper for handling await semantics with stanzas | ||||
|   final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter(); | ||||
|    | ||||
| @ -217,11 +223,9 @@ class XmppConnection { | ||||
|   /// none can be found. | ||||
|   T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) => _featureNegotiators[id] as T?; | ||||
|    | ||||
|   /// Registers an [XmppManagerBase] sub-class as a manager on this connection. | ||||
|   /// [sortHandlers] should NOT be touched. It specified if the handler priorities | ||||
|   /// should be set up. The only time this should be false is when called via | ||||
|   /// [registerManagers]. | ||||
|   void registerManager(XmppManagerBase manager, { bool sortHandlers = true }) { | ||||
|   /// Registers a list of [XmppManagerBase] sub-classes as managers on this connection. | ||||
|   Future<void> registerManagers(List<XmppManagerBase> managers) async { | ||||
|     for (final manager in managers) { | ||||
|       _log.finest('Registering ${manager.getId()}'); | ||||
|       manager.register( | ||||
|         XmppManagerAttributes( | ||||
| @ -241,39 +245,25 @@ class XmppConnection { | ||||
|       final id = manager.getId(); | ||||
|       _xmppManagers[id] = manager; | ||||
| 
 | ||||
|     if (id == discoManager) { | ||||
|       // NOTE: It is intentional that we do not exclude the [DiscoManager] from this | ||||
|       //       loop. It may also register features. | ||||
|       for (final registeredManager in _xmppManagers.values) { | ||||
|         (manager as DiscoManager).addDiscoFeatures(registeredManager.getDiscoFeatures()); | ||||
|       } | ||||
|     } else if (_xmppManagers.containsKey(discoManager)) { | ||||
|       (_xmppManagers[discoManager]! as DiscoManager).addDiscoFeatures(manager.getDiscoFeatures()); | ||||
|     } | ||||
| 
 | ||||
|       _incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers()); | ||||
|       _incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers()); | ||||
|       _outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers()); | ||||
|       _outgoingPostStanzaHandlers.addAll(manager.getOutgoingPostStanzaHandlers()); | ||||
|      | ||||
|     if (sortHandlers) { | ||||
|       _incomingStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|       _incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|       _outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|       _outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Like [registerManager], but for a list of managers. | ||||
|   void registerManagers(List<XmppManagerBase> managers) { | ||||
|     for (final manager in managers) { | ||||
|       registerManager(manager, sortHandlers: false); | ||||
|     } | ||||
| 
 | ||||
|     // Sort them | ||||
|     _incomingStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|     _incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|     _outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
|     _outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator); | ||||
| 
 | ||||
|     // Run the post register callbacks | ||||
|     for (final manager in _xmppManagers.values) { | ||||
|       if (!manager.initialized) { | ||||
|         _log.finest('Running post-registration callback for ${manager.getName()}'); | ||||
|         await manager.postRegisterCallback(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /// Register a list of negotiator with the connection. | ||||
| @ -378,7 +368,7 @@ class XmppConnection { | ||||
|     // Connect again | ||||
|     // ignore: cascade_invocations | ||||
|     _log.finest('Calling connect() from _attemptReconnection'); | ||||
|     await connect(); | ||||
|     await connect(waitForConnection: true); | ||||
|   } | ||||
|    | ||||
|   /// Called when a stream ending error has occurred | ||||
| @ -401,7 +391,11 @@ class XmppConnection { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (await _connectivityManager.hasConnection()) { | ||||
|       await _setConnectionState(XmppConnectionState.error); | ||||
|     } else { | ||||
|       await _setConnectionState(XmppConnectionState.notConnected); | ||||
|     } | ||||
|     await _reconnectionPolicy.onFailure(); | ||||
|   } | ||||
| 
 | ||||
| @ -832,7 +826,6 @@ class XmppConnection { | ||||
|         if (_isMandatoryNegotiationDone(_streamFeatures) && !_isNegotiationPossible(_streamFeatures)) { | ||||
|           _log.finest('Negotiations done!'); | ||||
|           _updateRoutingState(RoutingState.handleStanzas); | ||||
|           await _reconnectionPolicy.onSuccess(); | ||||
|           await _resetIsConnectionRunning(); | ||||
|           await _onNegotiationsDone(); | ||||
|         } else { | ||||
| @ -857,7 +850,6 @@ class XmppConnection { | ||||
|         _log.finest('Negotiations done!'); | ||||
| 
 | ||||
|         _updateRoutingState(RoutingState.handleStanzas); | ||||
|         await _reconnectionPolicy.onSuccess(); | ||||
|         await _resetIsConnectionRunning(); | ||||
|         await _onNegotiationsDone(); | ||||
|       } else { | ||||
| @ -875,7 +867,6 @@ class XmppConnection { | ||||
|       _log.finest('Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!'); | ||||
| 
 | ||||
|       _updateRoutingState(RoutingState.handleStanzas); | ||||
|       await _reconnectionPolicy.onSuccess(); | ||||
|       await _resetIsConnectionRunning(); | ||||
|       await _onNegotiationsDone(); | ||||
|       break; | ||||
| @ -987,7 +978,7 @@ class XmppConnection { | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _disconnect({required XmppConnectionState state, bool triggeredByUser = true}) async { | ||||
|     _reconnectionPolicy.setShouldReconnect(false); | ||||
|     await _reconnectionPolicy.setShouldReconnect(false); | ||||
| 
 | ||||
|     if (triggeredByUser) { | ||||
|       getPresenceManager().sendUnavailablePresence(); | ||||
| @ -1018,17 +1009,21 @@ class XmppConnection { | ||||
|    | ||||
|   /// Like [connect] but the Future resolves when the resource binding is either done or | ||||
|   /// SASL has failed. | ||||
|   Future<XmppConnectionResult> connectAwaitable({ String? lastResource }) async { | ||||
|   Future<XmppConnectionResult> connectAwaitable({ String? lastResource, bool waitForConnection = false }) async { | ||||
|     _runPreConnectionAssertions(); | ||||
|     await _resetIsConnectionRunning(); | ||||
|     _connectionCompleter = Completer(); | ||||
|     _log.finest('Calling connect() from connectAwaitable'); | ||||
|     await connect(lastResource: lastResource); | ||||
|     await connect( | ||||
|       lastResource: lastResource, | ||||
|       waitForConnection: waitForConnection, | ||||
|       shouldReconnect: false, | ||||
|     ); | ||||
|     return _connectionCompleter!.future; | ||||
|   } | ||||
|   | ||||
|   /// Start the connection process using the provided connection settings. | ||||
|   Future<void> connect({ String? lastResource }) async { | ||||
|   Future<void> connect({ String? lastResource, bool waitForConnection = false, bool shouldReconnect = true }) async { | ||||
|     if (_connectionState != XmppConnectionState.notConnected && _connectionState != XmppConnectionState.error) { | ||||
|       _log.fine('Cancelling this connection attempt as one appears to be already running.'); | ||||
|       return; | ||||
| @ -1036,15 +1031,25 @@ class XmppConnection { | ||||
|      | ||||
|     _runPreConnectionAssertions(); | ||||
|     await _resetIsConnectionRunning(); | ||||
|     _reconnectionPolicy.setShouldReconnect(true); | ||||
|      | ||||
|     if (lastResource != null) { | ||||
|       setResource(lastResource); | ||||
|     } | ||||
| 
 | ||||
|     if (shouldReconnect) { | ||||
|       await _reconnectionPolicy.setShouldReconnect(true); | ||||
|     } | ||||
| 
 | ||||
|     await _reconnectionPolicy.reset(); | ||||
|     await _sendEvent(ConnectingEvent()); | ||||
| 
 | ||||
|     // If requested, wait until we have a network connection | ||||
|     if (waitForConnection) { | ||||
|       _log.info('Waiting for okay from connectivityManager'); | ||||
|       await _connectivityManager.waitForConnection(); | ||||
|       _log.info('Got okay from connectivityManager'); | ||||
|     } | ||||
|      | ||||
|     final smManager = getStreamManagementManager(); | ||||
|     String? host; | ||||
|     int? port; | ||||
|  | ||||
							
								
								
									
										18
									
								
								packages/moxxmpp/lib/src/connectivity.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/moxxmpp/lib/src/connectivity.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| /// This manager class is responsible to tell the moxxmpp XmppConnection | ||||
| /// when a connection can be established or not, regarding the network availability. | ||||
| abstract class ConnectivityManager { | ||||
|   /// Returns true if a network connection is available. If not, returns false. | ||||
|   Future<bool> hasConnection(); | ||||
| 
 | ||||
|   /// Returns a future that resolves once we have a network connection. | ||||
|   Future<void> waitForConnection(); | ||||
| } | ||||
| 
 | ||||
| /// An implementation of [ConnectivityManager] that is always connected. | ||||
| class AlwaysConnectedConnectivityManager extends ConnectivityManager { | ||||
|   @override | ||||
|   Future<bool> hasConnection() async => true; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> waitForConnection() async {} | ||||
| } | ||||
| @ -1,14 +1,21 @@ | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
| import 'package:moxxmpp/src/events.dart'; | ||||
| import 'package:moxxmpp/src/managers/attributes.dart'; | ||||
| import 'package:moxxmpp/src/managers/data.dart'; | ||||
| import 'package:moxxmpp/src/managers/handlers.dart'; | ||||
| import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/stringxml.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; | ||||
| 
 | ||||
| abstract class XmppManagerBase { | ||||
|   late final XmppManagerAttributes _managerAttributes; | ||||
|   late final Logger _log; | ||||
| 
 | ||||
|   /// Flag indicating that the post registration callback has been called once. | ||||
|   bool initialized = false; | ||||
|    | ||||
|   /// Registers the callbacks from XmppConnection with the manager | ||||
|   void register(XmppManagerAttributes attributes) { | ||||
|     _managerAttributes = attributes; | ||||
| @ -49,6 +56,9 @@ abstract class XmppManagerBase { | ||||
|   /// Return a list of features that should be included in a disco response. | ||||
|   List<String> getDiscoFeatures() => []; | ||||
| 
 | ||||
|   /// Return a list of identities that should be included in a disco response. | ||||
|   List<Identity> getDiscoIdentities() => []; | ||||
|    | ||||
|   /// Return the Id (akin to xmlns) of this manager. | ||||
|   String getId(); | ||||
| 
 | ||||
| @ -64,6 +74,24 @@ abstract class XmppManagerBase { | ||||
|   /// Returns true if the XEP is supported on the server. If not, returns false | ||||
|   Future<bool> isSupported(); | ||||
| 
 | ||||
|   /// Called after the registration of all managers against the XmppConnection is done. | ||||
|   /// This method is only called once during the entire lifetime of it. | ||||
|   @mustCallSuper | ||||
|   Future<void> postRegisterCallback() async { | ||||
|     initialized = true; | ||||
| 
 | ||||
|     final disco = getAttributes().getManagerById<DiscoManager>(discoManager); | ||||
|     if (disco != null) { | ||||
|       if (getDiscoFeatures().isNotEmpty) { | ||||
|         disco.addFeatures(getDiscoFeatures()); | ||||
|       } | ||||
| 
 | ||||
|       if (getDiscoIdentities().isNotEmpty) { | ||||
|         disco.addIdentities(getDiscoIdentities()); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   /// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if | ||||
|   /// the nonza has been handled by one of the handlers. Resolves to false otherwise. | ||||
|   Future<bool> runNonzaHandlers(XMLNode nonza) async { | ||||
|  | ||||
| @ -1,25 +1,25 @@ | ||||
| const smManager = 'im.moxxmpp.streammangementmanager'; | ||||
| const discoManager = 'im.moxxmpp.discomanager'; | ||||
| const messageManager = 'im.moxxmpp.messagemanager'; | ||||
| const rosterManager = 'im.moxxmpp.rostermanager'; | ||||
| const presenceManager = 'im.moxxmpp.presencemanager'; | ||||
| const csiManager = 'im.moxxmpp.csimanager'; | ||||
| const carbonsManager = 'im.moxxmpp.carbonsmanager'; | ||||
| const vcardManager = 'im.moxxmpp.vcardmanager'; | ||||
| const pubsubManager = 'im.moxxmpp.pubsubmanager'; | ||||
| const userAvatarManager = 'im.moxxmpp.useravatarmanager'; | ||||
| const stableIdManager = 'im.moxxmpp.stableidmanager'; | ||||
| const simsManager = 'im.moxxmpp.simsmanager'; | ||||
| const messageDeliveryReceiptManager = 'im.moxxmpp.messagedeliveryreceiptmanager'; | ||||
| const chatMarkerManager = 'im.moxxmpp.chatmarkermanager'; | ||||
| const oobManager = 'im.moxxmpp.oobmanager'; | ||||
| const sfsManager = 'im.moxxmpp.sfsmanager'; | ||||
| const messageRepliesManager = 'im.moxxmpp.messagerepliesmanager'; | ||||
| const blockingManager = 'im.moxxmpp.blockingmanager'; | ||||
| const httpFileUploadManager = 'im.moxxmpp.httpfileuploadmanager'; | ||||
| const chatStateManager = 'im.moxxmpp.chatstatemanager'; | ||||
| const pingManager = 'im.moxxmpp.ping'; | ||||
| const fileUploadNotificationManager = 'im.moxxmpp.fileuploadnotificationmanager'; | ||||
| const smManager = 'org.moxxmpp.streammangementmanager'; | ||||
| const discoManager = 'org.moxxmpp.discomanager'; | ||||
| const messageManager = 'org.moxxmpp.messagemanager'; | ||||
| const rosterManager = 'org.moxxmpp.rostermanager'; | ||||
| const presenceManager = 'org.moxxmpp.presencemanager'; | ||||
| const csiManager = 'org.moxxmpp.csimanager'; | ||||
| const carbonsManager = 'org.moxxmpp.carbonsmanager'; | ||||
| const vcardManager = 'org.moxxmpp.vcardmanager'; | ||||
| const pubsubManager = 'org.moxxmpp.pubsubmanager'; | ||||
| const userAvatarManager = 'org.moxxmpp.useravatarmanager'; | ||||
| const stableIdManager = 'org.moxxmpp.stableidmanager'; | ||||
| const simsManager = 'org.moxxmpp.simsmanager'; | ||||
| const messageDeliveryReceiptManager = 'org.moxxmpp.messagedeliveryreceiptmanager'; | ||||
| const chatMarkerManager = 'org.moxxmpp.chatmarkermanager'; | ||||
| const oobManager = 'org.moxxmpp.oobmanager'; | ||||
| const sfsManager = 'org.moxxmpp.sfsmanager'; | ||||
| const messageRepliesManager = 'org.moxxmpp.messagerepliesmanager'; | ||||
| const blockingManager = 'org.moxxmpp.blockingmanager'; | ||||
| const httpFileUploadManager = 'org.moxxmpp.httpfileuploadmanager'; | ||||
| const chatStateManager = 'org.moxxmpp.chatstatemanager'; | ||||
| const pingManager = 'org.moxxmpp.ping'; | ||||
| const fileUploadNotificationManager = 'org.moxxmpp.fileuploadnotificationmanager'; | ||||
| const omemoManager = 'org.moxxmpp.omemomanager'; | ||||
| const emeManager = 'org.moxxmpp.ememanager'; | ||||
| const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager'; | ||||
| @ -28,3 +28,4 @@ const messageRetractionManager = 'org.moxxmpp.messageretractionmanager'; | ||||
| const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager'; | ||||
| const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager'; | ||||
| const stickersManager = 'org.moxxmpp.stickersmanager'; | ||||
| const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities'; | ||||
|  | ||||
| @ -8,17 +8,19 @@ import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/stanza.dart'; | ||||
| import 'package:moxxmpp/src/stringxml.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0115.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0414.dart'; | ||||
| 
 | ||||
| /// A function that will be called when presence, outside of subscription request | ||||
| /// management, will be sent. Useful for managers that want to add [XMLNode]s to said | ||||
| /// presence. | ||||
| typedef PresencePreSendCallback = Future<List<XMLNode>> Function(); | ||||
| 
 | ||||
| /// A mandatory manager that handles initial presence sending, sending of subscription | ||||
| /// request management requests and triggers events for incoming presence stanzas. | ||||
| class PresenceManager extends XmppManagerBase { | ||||
|   PresenceManager(this._capHashNode) : _capabilityHash = null, super(); | ||||
|   String? _capabilityHash; | ||||
|   final String _capHashNode; | ||||
|   PresenceManager() : super(); | ||||
| 
 | ||||
|   String get capabilityHashNode => _capHashNode; | ||||
|   /// The list of pre-send callbacks. | ||||
|   final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true); | ||||
|    | ||||
|   @override | ||||
|   String getId() => presenceManager; | ||||
| @ -40,6 +42,11 @@ class PresenceManager extends XmppManagerBase { | ||||
|   @override | ||||
|   Future<bool> isSupported() async => true; | ||||
| 
 | ||||
|   /// Register the pre-send callback [callback]. | ||||
|   void registerPreSendCallback(PresencePreSendCallback callback) { | ||||
|     _presenceCallbacks.add(callback); | ||||
|   } | ||||
|    | ||||
|   Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async { | ||||
|     final attrs = getAttributes(); | ||||
|     switch (presence.type) { | ||||
| @ -63,43 +70,26 @@ class PresenceManager extends XmppManagerBase { | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns the capability hash. | ||||
|   Future<String> getCapabilityHash() async { | ||||
|     final manager = getAttributes().getManagerById(discoManager)! as DiscoManager; | ||||
|     _capabilityHash ??= await calculateCapabilityHash( | ||||
|       DiscoInfo( | ||||
|         manager.getRegisteredDiscoFeatures(), | ||||
|         manager.getIdentities(), | ||||
|         [], | ||||
|         getAttributes().getFullJID(), | ||||
|       ), | ||||
|       getHashByName('sha-1')!, | ||||
|     ); | ||||
| 
 | ||||
|     return _capabilityHash!; | ||||
|   } | ||||
|    | ||||
|   /// Sends the initial presence to enable receiving messages. | ||||
|   Future<void> sendInitialPresence() async { | ||||
|     final attrs = getAttributes(); | ||||
|     attrs.sendNonza( | ||||
|       Stanza.presence( | ||||
|         from: attrs.getFullJID().toString(), | ||||
|         children: [ | ||||
|     final children = List<XMLNode>.from([ | ||||
|       XMLNode( | ||||
|         tag: 'show', | ||||
|         text: 'chat', | ||||
|       ), | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'c', | ||||
|             xmlns: capsXmlns, | ||||
|             attributes: { | ||||
|               'hash': 'sha-1', | ||||
|               'node': _capHashNode, | ||||
|               'ver': await getCapabilityHash() | ||||
|             }, | ||||
|           ) | ||||
|         ], | ||||
|     ]); | ||||
| 
 | ||||
|     for (final callback in _presenceCallbacks) { | ||||
|       children.addAll( | ||||
|         await callback(), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     final attrs = getAttributes(); | ||||
|     attrs.sendNonza( | ||||
|       Stanza.presence( | ||||
|         from: attrs.getFullJID().toString(), | ||||
|         children: children, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -2,6 +2,7 @@ import 'dart:async'; | ||||
| import 'dart:math'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
| import 'package:moxxmpp/src/util/queue.dart'; | ||||
| import 'package:synchronized/synchronized.dart'; | ||||
| 
 | ||||
| /// A callback function to be called when the connection to the server has been lost. | ||||
| @ -24,10 +25,16 @@ abstract class ReconnectionPolicy { | ||||
|   bool _shouldAttemptReconnection = false; | ||||
| 
 | ||||
|   /// Indicate if a reconnection attempt is currently running. | ||||
|   bool _isReconnecting = false; | ||||
|   @protected | ||||
|   bool isReconnecting = false; | ||||
| 
 | ||||
|   /// And the corresponding lock | ||||
|   final Lock _isReconnectingLock = Lock(); | ||||
|   @protected | ||||
|   final Lock lock = Lock(); | ||||
| 
 | ||||
|   /// The lock for accessing [_shouldAttemptReconnection] | ||||
|   @protected | ||||
|   final Lock shouldReconnectLock = Lock(); | ||||
|    | ||||
|   /// Called by XmppConnection to register the policy. | ||||
|   void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) { | ||||
| @ -48,96 +55,121 @@ abstract class ReconnectionPolicy { | ||||
|   /// Caled by the XmppConnection when the reconnection was successful. | ||||
|   Future<void> onSuccess(); | ||||
| 
 | ||||
|   bool get shouldReconnect => _shouldAttemptReconnection; | ||||
|   Future<bool> getShouldReconnect() async { | ||||
|     return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection); | ||||
|   } | ||||
| 
 | ||||
|   /// Set whether a reconnection attempt should be made. | ||||
|   void setShouldReconnect(bool value) { | ||||
|     _shouldAttemptReconnection = value; | ||||
|   Future<void> setShouldReconnect(bool value) async { | ||||
|     return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection = value); | ||||
|   } | ||||
| 
 | ||||
|   /// Returns true if the manager is currently triggering a reconnection. If not, returns | ||||
|   /// false. | ||||
|   Future<bool> isReconnectionRunning() async { | ||||
|     return _isReconnectingLock.synchronized(() => _isReconnecting); | ||||
|     return lock.synchronized(() => isReconnecting); | ||||
|   } | ||||
| 
 | ||||
|   /// Set the _isReconnecting state to [value]. | ||||
|   /// Set the isReconnecting state to [value]. | ||||
|   @protected | ||||
|   Future<void> setIsReconnecting(bool value) async { | ||||
|     await _isReconnectingLock.synchronized(() async { | ||||
|       _isReconnecting = value; | ||||
|     await lock.synchronized(() async { | ||||
|       isReconnecting = value; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @protected | ||||
|   Future<bool> testAndSetIsReconnecting() async { | ||||
|     return _isReconnectingLock.synchronized(() { | ||||
|       if (_isReconnecting) { | ||||
|         return false; | ||||
|       } else { | ||||
|         _isReconnecting = true; | ||||
|         return true; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// A simple reconnection strategy: Make the reconnection delays exponentially longer | ||||
| /// for every failed attempt. | ||||
| /// NOTE: This ReconnectionPolicy may be broken | ||||
| class ExponentialBackoffReconnectionPolicy extends ReconnectionPolicy { | ||||
|   ExponentialBackoffReconnectionPolicy(this._maxBackoffTime) : super(); | ||||
| class RandomBackoffReconnectionPolicy extends ReconnectionPolicy { | ||||
|   RandomBackoffReconnectionPolicy( | ||||
|     this._minBackoffTime, | ||||
|     this._maxBackoffTime, | ||||
|   ) : assert(_minBackoffTime < _maxBackoffTime, '_minBackoffTime must be smaller than _maxBackoffTime'), | ||||
|       super(); | ||||
| 
 | ||||
|   /// The maximum time in seconds that a backoff step should be. | ||||
|   /// The maximum time in seconds that a backoff should be. | ||||
|   final int _maxBackoffTime; | ||||
| 
 | ||||
|   /// Amount of consecutive failed reconnections. | ||||
|   int _counter = 0; | ||||
|   /// The minimum time in seconds that a backoff should be. | ||||
|   final int _minBackoffTime; | ||||
| 
 | ||||
|   /// Backoff timer. | ||||
|   Timer? _timer; | ||||
| 
 | ||||
|   final Lock _timerLock = Lock(); | ||||
| 
 | ||||
|   /// Logger. | ||||
|   final Logger _log = Logger('ExponentialBackoffReconnectionPolicy'); | ||||
|   final Logger _log = Logger('RandomBackoffReconnectionPolicy'); | ||||
| 
 | ||||
|   /// Event queue | ||||
|   final AsyncQueue _eventQueue = AsyncQueue(); | ||||
| 
 | ||||
|   /// Called when the backoff expired | ||||
|   Future<void> _onTimerElapsed() async { | ||||
|     final isReconnecting = await isReconnectionRunning(); | ||||
|     if (shouldReconnect) { | ||||
|       if (!isReconnecting) { | ||||
|         await setIsReconnecting(true); | ||||
|     _log.fine('Timer elapsed. Waiting for lock'); | ||||
|     await lock.synchronized(() async { | ||||
|       _log.fine('Lock aquired'); | ||||
|       if (!(await getShouldReconnect())) { | ||||
|         _log.fine('Backoff timer expired but getShouldReconnect() returned false'); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (isReconnecting) { | ||||
|         _log.fine('Backoff timer expired but a reconnection is running, so doing nothing.'); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       _log.fine('Triggering reconnect'); | ||||
|       isReconnecting = true; | ||||
|       await performReconnect!(); | ||||
|       } else { | ||||
|         // Should never happen. | ||||
|         _log.fine('Backoff timer expired but reconnection is running, so doing nothing.'); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     await _timerLock.synchronized(() { | ||||
|       _timer?.cancel(); | ||||
|       _timer = null; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _reset() async { | ||||
|     _log.finest('Resetting internal state'); | ||||
| 
 | ||||
|     await _timerLock.synchronized(() { | ||||
|       _timer?.cancel(); | ||||
|       _timer = null; | ||||
|     }); | ||||
| 
 | ||||
|     await setIsReconnecting(false); | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   Future<void> reset() async { | ||||
|     _log.finest('Resetting internal state'); | ||||
|     _counter = 0; | ||||
|     await setIsReconnecting(false); | ||||
| 
 | ||||
|     if (_timer != null) { | ||||
|       _timer!.cancel(); | ||||
|       _timer = null; | ||||
|     // ignore: unnecessary_lambdas | ||||
|     await _eventQueue.addJob(() => _reset()); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _onFailure() async { | ||||
|     final shouldContinue = await _timerLock.synchronized(() { | ||||
|       return _timer == null; | ||||
|     }); | ||||
|     if (!shouldContinue) { | ||||
|       _log.finest('_onFailure: Not backing off since _timer is already running'); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     final seconds = Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime; | ||||
|     _log.finest('Failure occured. Starting random backoff with ${seconds}s'); | ||||
|     _timer?.cancel(); | ||||
| 
 | ||||
|     _timer = Timer(Duration(seconds: seconds), _onTimerElapsed); | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   Future<void> onFailure() async { | ||||
|     _log.finest('Failure occured. Starting exponential backoff'); | ||||
|     _counter++; | ||||
| 
 | ||||
|     if (_timer != null) { | ||||
|       _timer!.cancel(); | ||||
|     } | ||||
| 
 | ||||
|     // Wait at max 80 seconds. | ||||
|     final seconds = min(min(pow(2, _counter).toInt(), 80), _maxBackoffTime); | ||||
|     _timer = Timer(Duration(seconds: seconds), _onTimerElapsed); | ||||
|     // ignore: unnecessary_lambdas | ||||
|     await _eventQueue.addJob(() => _onFailure()); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|  | ||||
							
								
								
									
										56
									
								
								packages/moxxmpp/lib/src/util/queue.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								packages/moxxmpp/lib/src/util/queue.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:collection'; | ||||
| import 'package:meta/meta.dart'; | ||||
| import 'package:synchronized/synchronized.dart'; | ||||
| 
 | ||||
| /// A job to be submitted to an [AsyncQueue]. | ||||
| typedef AsyncQueueJob = Future<void> Function(); | ||||
| 
 | ||||
| /// A (hopefully) async-safe queue that attempts to force | ||||
| /// in-order execution of its jobs. | ||||
| class AsyncQueue { | ||||
|   /// The lock for accessing [AsyncQueue._lock] and [AsyncQueue._running]. | ||||
|   final Lock _lock = Lock(); | ||||
| 
 | ||||
|   /// The actual job queue. | ||||
|   final Queue<AsyncQueueJob> _queue = Queue<AsyncQueueJob>(); | ||||
|    | ||||
|   /// Indicates whether we are currently executing a job. | ||||
|   bool _running = false; | ||||
| 
 | ||||
|   @visibleForTesting | ||||
|   Queue<AsyncQueueJob> get queue => _queue; | ||||
| 
 | ||||
|   @visibleForTesting | ||||
|   bool get isRunning => _running; | ||||
|    | ||||
|   /// Adds a job [job] to the queue. | ||||
|   Future<void> addJob(AsyncQueueJob job) async { | ||||
|     await _lock.synchronized(() { | ||||
|       _queue.add(job); | ||||
| 
 | ||||
|       if (!_running && _queue.isNotEmpty) { | ||||
|         _running = true; | ||||
|         unawaited(_popJob()); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> clear() async { | ||||
|     await _lock.synchronized(_queue.clear); | ||||
|   } | ||||
|    | ||||
|   Future<void> _popJob() async { | ||||
|     final job = _queue.removeFirst(); | ||||
|     final future = job(); | ||||
|     await future; | ||||
| 
 | ||||
|     await _lock.synchronized(() { | ||||
|       if (_queue.isNotEmpty) { | ||||
|         unawaited(_popJob()); | ||||
|       } else { | ||||
|         _running = false; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -3,7 +3,6 @@ import 'package:moxxmpp/src/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/stringxml.dart'; | ||||
| 
 | ||||
| class DataFormOption { | ||||
| 
 | ||||
|   const DataFormOption({ required this.value, this.label }); | ||||
|   final String? label; | ||||
|   final String value; | ||||
| @ -23,7 +22,6 @@ class DataFormOption { | ||||
| } | ||||
| 
 | ||||
| class DataFormField { | ||||
| 
 | ||||
|   const DataFormField({ | ||||
|       required this.options, | ||||
|       required this.values, | ||||
| @ -60,7 +58,6 @@ class DataFormField { | ||||
| } | ||||
| 
 | ||||
| class DataForm { | ||||
| 
 | ||||
|   const DataForm({ | ||||
|       required this.type, | ||||
|       required this.instructions, | ||||
|  | ||||
| @ -1,9 +1,10 @@ | ||||
| import 'package:meta/meta.dart'; | ||||
| import 'package:moxxmpp/src/jid.dart'; | ||||
| import 'package:moxxmpp/src/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/stringxml.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0004.dart'; | ||||
| 
 | ||||
| class Identity { | ||||
| 
 | ||||
|   const Identity({ required this.category, required this.type, this.name, this.lang }); | ||||
|   final String category; | ||||
|   final String type; | ||||
| @ -23,24 +24,96 @@ class Identity { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @immutable | ||||
| class DiscoInfo { | ||||
| 
 | ||||
|   const DiscoInfo( | ||||
|     this.features, | ||||
|     this.identities, | ||||
|     this.extendedInfo, | ||||
|     this.node, | ||||
|     this.jid, | ||||
|   ); | ||||
| 
 | ||||
|   factory DiscoInfo.fromQuery(XMLNode query, JID jid) { | ||||
|     final features = List<String>.empty(growable: true); | ||||
|     final identities = List<Identity>.empty(growable: true); | ||||
|     final extendedInfo = List<DataForm>.empty(growable: true); | ||||
| 
 | ||||
|     for (final element in query.children) { | ||||
|       if (element.tag == 'feature') { | ||||
|         features.add(element.attributes['var']! as String); | ||||
|       } else if (element.tag == 'identity') { | ||||
|         identities.add( | ||||
|           Identity( | ||||
|             category: element.attributes['category']! as String, | ||||
|             type: element.attributes['type']! as String, | ||||
|             name: element.attributes['name'] as String?, | ||||
|           ), | ||||
|         ); | ||||
|       } else if (element.tag == 'x' && element.attributes['xmlns'] == dataFormsXmlns) { | ||||
|         extendedInfo.add( | ||||
|           parseDataForm(element), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return DiscoInfo( | ||||
|       features, | ||||
|       identities, | ||||
|       extendedInfo, | ||||
|       query.attributes['node'] as String?, | ||||
|       jid, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   final List<String> features; | ||||
|   final List<Identity> identities; | ||||
|   final List<DataForm> extendedInfo; | ||||
|   final JID jid; | ||||
|   final String? node; | ||||
|   final JID? jid; | ||||
| 
 | ||||
|   XMLNode toXml() { | ||||
|     return XMLNode.xmlns( | ||||
|       tag: 'query', | ||||
|       xmlns: discoInfoXmlns, | ||||
|       attributes: node != null ? | ||||
|         <String, String>{ 'node': node!, } : | ||||
|         <String, String>{}, | ||||
|       children: [ | ||||
|         ...identities.map((identity) => identity.toXMLNode()), | ||||
|         ...features.map((feature) => XMLNode( | ||||
|           tag: 'feature', | ||||
|           attributes: { 'var': feature, }, | ||||
|         ),), | ||||
|          | ||||
|         if (extendedInfo.isNotEmpty) | ||||
|           ...extendedInfo.map((ei) => ei.toXml()), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @immutable | ||||
| class DiscoItem { | ||||
| 
 | ||||
|   const DiscoItem({ required this.jid, this.node, this.name }); | ||||
|   final String jid; | ||||
|   final String? node; | ||||
|   final String? name; | ||||
| 
 | ||||
|   XMLNode toXml() { | ||||
|     final attributes = { | ||||
|       'jid': jid, | ||||
|     }; | ||||
|     if (node != null) { | ||||
|       attributes['node'] = node!; | ||||
|     } | ||||
|     if (name != null) { | ||||
|       attributes['name'] = name!; | ||||
|     } | ||||
| 
 | ||||
|     return XMLNode( | ||||
|       tag: 'node', | ||||
|       attributes: attributes, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -7,17 +7,21 @@ import 'package:moxxmpp/src/managers/data.dart'; | ||||
| import 'package:moxxmpp/src/managers/handlers.dart'; | ||||
| import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/presence.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_0004.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/helpers.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0115.dart'; | ||||
| import 'package:synchronized/synchronized.dart'; | ||||
| 
 | ||||
| /// Callback that is called when a disco#info requests is received on a given node. | ||||
| typedef DiscoInfoRequestCallback = Future<DiscoInfo> Function(); | ||||
| 
 | ||||
| /// Callback that is called when a disco#items requests is received on a given node. | ||||
| typedef DiscoItemsRequestCallback = Future<List<DiscoItem>> Function(); | ||||
| 
 | ||||
| @immutable | ||||
| class DiscoCacheKey { | ||||
|   const DiscoCacheKey(this.jid, this.node); | ||||
| @ -33,32 +37,48 @@ class DiscoCacheKey { | ||||
|   int get hashCode => jid.hashCode ^ node.hashCode; | ||||
| } | ||||
| 
 | ||||
| /// This manager implements XEP-0030 by providing a way of performing disco#info and | ||||
| /// disco#items requests and answering those requests. | ||||
| /// A caching mechanism is also provided. | ||||
| class DiscoManager extends XmppManagerBase { | ||||
|   DiscoManager() | ||||
|     : _features = List.empty(growable: true), | ||||
|       _capHashCache = {}, | ||||
|       _capHashInfoCache = {}, | ||||
|       _discoInfoCache = {}, | ||||
|       _runningInfoQueries = {}, | ||||
|       _cacheLock = Lock(), | ||||
|   /// [identities] is a list of disco identities that should be added by default | ||||
|   /// to a disco#info response. | ||||
|   DiscoManager(List<Identity> identities) | ||||
|     : _identities = List<Identity>.from(identities), | ||||
|       super(); | ||||
| 
 | ||||
|   /// Our features | ||||
|   final List<String> _features; | ||||
|   final List<String> _features = List.empty(growable: true); | ||||
| 
 | ||||
|   /// Disco identities that we advertise | ||||
|   final List<Identity> _identities; | ||||
|    | ||||
|   /// Map full JID to Capability hashes | ||||
|   final Map<String, CapabilityHashInfo> _capHashCache; | ||||
|   final Map<String, CapabilityHashInfo> _capHashCache = {}; | ||||
| 
 | ||||
|   /// Map capability hash to the disco info | ||||
|   final Map<String, DiscoInfo> _capHashInfoCache; | ||||
|   final Map<String, DiscoInfo> _capHashInfoCache = {}; | ||||
| 
 | ||||
|   /// Map full JID to Disco Info | ||||
|   final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache; | ||||
|   final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {}; | ||||
| 
 | ||||
|   /// Mapping the full JID to a list of running requests | ||||
|   final Map<DiscoCacheKey, List<Completer<Result<DiscoError, DiscoInfo>>>> _runningInfoQueries; | ||||
|   final Map<DiscoCacheKey, List<Completer<Result<DiscoError, DiscoInfo>>>> _runningInfoQueries = {}; | ||||
| 
 | ||||
|   /// Cache lock | ||||
|   final Lock _cacheLock; | ||||
|   final Lock _cacheLock = Lock(); | ||||
| 
 | ||||
|   /// disco#info callbacks: node -> Callback | ||||
|   final Map<String, DiscoInfoRequestCallback> _discoInfoCallbacks = {}; | ||||
| 
 | ||||
|   /// disco#items callbacks: node -> Callback | ||||
|   final Map<String, DiscoItemsRequestCallback> _discoItemsCallbacks = {}; | ||||
| 
 | ||||
|   /// The list of identities that are registered. | ||||
|   List<Identity> get identities => _identities; | ||||
| 
 | ||||
|   /// The list of disco features that are registered. | ||||
|   List<String> get features => _features; | ||||
|    | ||||
|   @visibleForTesting | ||||
|   bool hasInfoQueriesRunning() => _runningInfoQueries.isNotEmpty; | ||||
| @ -106,9 +126,19 @@ class DiscoManager extends XmppManagerBase { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Register a callback [callback] for a disco#info query on [node]. | ||||
|   void registerInfoCallback(String node, DiscoInfoRequestCallback callback) { | ||||
|     _discoInfoCallbacks[node] = callback; | ||||
|   } | ||||
| 
 | ||||
|   /// Register a callback [callback] for a disco#items query on [node]. | ||||
|   void registerItemsCallback(String node, DiscoItemsRequestCallback callback) { | ||||
|     _discoItemsCallbacks[node] = callback; | ||||
|   } | ||||
|    | ||||
|   /// Adds a list of features to the possible disco info response. | ||||
|   /// This function only adds features that are not already present in the disco features. | ||||
|   void addDiscoFeatures(List<String> features) { | ||||
|   void addFeatures(List<String> features) { | ||||
|     for (final feat in features) { | ||||
|       if (!_features.contains(feat)) { | ||||
|         _features.add(feat); | ||||
| @ -116,6 +146,16 @@ class DiscoManager extends XmppManagerBase { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /// Adds a list of identities to the possible disco info response. | ||||
|   /// This function only adds features that are not already present in the disco features. | ||||
|   void addIdentities(List<Identity> identities) { | ||||
|     for (final identity in identities) { | ||||
|       if (!_identities.contains(identity)) { | ||||
|         _identities.add(identity); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   Future<void> _onPresence(JID from, Stanza presence) async { | ||||
|     final c = presence.firstTag('c', xmlns: capsXmlns); | ||||
|     if (c == null) return; | ||||
| @ -146,45 +186,33 @@ class DiscoManager extends XmppManagerBase { | ||||
|     }); | ||||
|   } | ||||
|    | ||||
|   /// Returns the list of disco features registered. | ||||
|   List<String> getRegisteredDiscoFeatures() => _features; | ||||
|    | ||||
|   /// May be overriden. Specifies the identities which will be returned in a disco info response. | ||||
|   List<Identity> getIdentities() => const [ Identity(category: 'client', type: 'pc', name: 'moxxmpp', lang: 'en') ]; | ||||
|   /// Returns the [DiscoInfo] object that would be used as the response to a disco#info | ||||
|   /// query against our bare JID with no node. The results node attribute is set | ||||
|   /// to [node]. | ||||
|   DiscoInfo getDiscoInfo(String? node) { | ||||
|     return DiscoInfo( | ||||
|       _features, | ||||
|       _identities, | ||||
|       const [], | ||||
|       node, | ||||
|       null, | ||||
|     ); | ||||
|   } | ||||
|    | ||||
|   Future<StanzaHandlerData> _onDiscoInfoRequest(Stanza stanza, StanzaHandlerData state) async { | ||||
|     if (stanza.type != 'get') return state; | ||||
| 
 | ||||
|     final presence = getAttributes().getManagerById(presenceManager)! as PresenceManager; | ||||
|     final query = stanza.firstTag('query', xmlns: discoInfoXmlns)!; | ||||
|     final node = query.attributes['node'] as String?; | ||||
|     final capHash = await presence.getCapabilityHash(); | ||||
|     final isCapabilityNode = node == '${presence.capabilityHashNode}#$capHash'; | ||||
| 
 | ||||
|     if (!isCapabilityNode && node != null) { | ||||
|     if (_discoInfoCallbacks.containsKey(node)) { | ||||
|       // We can now assume that node != null | ||||
|       final result = await _discoInfoCallbacks[node]!(); | ||||
|       await reply( | ||||
|         state, | ||||
|         'error', | ||||
|         'result', | ||||
|         [ | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'query', | ||||
|             xmlns: discoInfoXmlns, | ||||
|             attributes: <String, String>{ | ||||
|               'node': node | ||||
|             }, | ||||
|           ), | ||||
|           XMLNode( | ||||
|             tag: 'error', | ||||
|             attributes: <String, String>{ | ||||
|               'type': 'cancel' | ||||
|             }, | ||||
|             children: [ | ||||
|               XMLNode.xmlns( | ||||
|                 tag: 'not-allowed', | ||||
|                 xmlns: fullStanzaXmlns, | ||||
|               ) | ||||
|             ], | ||||
|           ), | ||||
|           result.toXml(), | ||||
|         ], | ||||
|       ); | ||||
| 
 | ||||
| @ -195,24 +223,7 @@ class DiscoManager extends XmppManagerBase { | ||||
|       state, | ||||
|       'result', | ||||
|       [ | ||||
|         XMLNode.xmlns( | ||||
|           tag: 'query', | ||||
|           xmlns: discoInfoXmlns, | ||||
|           attributes: { | ||||
|             ...!isCapabilityNode ? {} : { | ||||
|               'node': '${presence.capabilityHashNode}#$capHash' | ||||
|             } | ||||
|           }, | ||||
|           children: [ | ||||
|             ...getIdentities().map((identity) => identity.toXMLNode()), | ||||
|             ..._features.map((feat) { | ||||
|               return XMLNode( | ||||
|                 tag: 'feature', | ||||
|                 attributes: <String, dynamic>{ 'var': feat }, | ||||
|               ); | ||||
|             }), | ||||
|           ], | ||||
|         ), | ||||
|         getDiscoInfo(node).toXml(), | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
| @ -223,37 +234,9 @@ class DiscoManager extends XmppManagerBase { | ||||
|     if (stanza.type != 'get') return state; | ||||
| 
 | ||||
|     final query = stanza.firstTag('query', xmlns: discoItemsXmlns)!; | ||||
|     if (query.attributes['node'] != null) { | ||||
|       // TODO(Unknown): Handle the node we specified for XEP-0115 | ||||
|       await reply( | ||||
|         state, | ||||
|         'error', | ||||
|         [ | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'query', | ||||
|             xmlns: discoItemsXmlns, | ||||
|             attributes: <String, String>{ | ||||
|               'node': query.attributes['node']! as String, | ||||
|             }, | ||||
|           ), | ||||
|           XMLNode( | ||||
|             tag: 'error', | ||||
|             attributes: <String, dynamic>{ | ||||
|               'type': 'cancel' | ||||
|             }, | ||||
|             children: [ | ||||
|               XMLNode.xmlns( | ||||
|                 tag: 'not-allowed', | ||||
|                 xmlns: fullStanzaXmlns, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
| 
 | ||||
|       return state.copyWith(done: true); | ||||
|     } | ||||
| 
 | ||||
|     final node = query.attributes['node'] as String?; | ||||
|     if (_discoItemsCallbacks.containsKey(node)) { | ||||
|       final result = await _discoItemsCallbacks[node]!(); | ||||
|       await reply( | ||||
|         state, | ||||
|         'result', | ||||
| @ -261,6 +244,10 @@ class DiscoManager extends XmppManagerBase { | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'query', | ||||
|             xmlns: discoItemsXmlns, | ||||
|             attributes: <String, String>{ | ||||
|               'node': node!, | ||||
|             }, | ||||
|             children: result.map((item) => item.toXml()).toList(), | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
| @ -268,6 +255,9 @@ class DiscoManager extends XmppManagerBase { | ||||
|       return state.copyWith(done: true); | ||||
|     } | ||||
| 
 | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async { | ||||
|     return _cacheLock.synchronized(() async { | ||||
|       // Complete all futures | ||||
| @ -322,34 +312,17 @@ class DiscoManager extends XmppManagerBase { | ||||
|       return result; | ||||
|     } | ||||
| 
 | ||||
|     final error = stanza.firstTag('error'); | ||||
|     if (error != null && stanza.attributes['type'] == 'error') { | ||||
|     if (stanza.attributes['type'] == 'error') { | ||||
|       //final error = stanza.firstTag('error'); | ||||
|       final result = Result<DiscoError, DiscoInfo>(ErrorResponseDiscoError()); | ||||
|       await _exitDiscoInfoCriticalSection(cacheKey, result); | ||||
|       return result; | ||||
|     } | ||||
|      | ||||
|     final features = List<String>.empty(growable: true); | ||||
|     final identities = List<Identity>.empty(growable: true); | ||||
| 
 | ||||
|     for (final element in query.children) { | ||||
|       if (element.tag == 'feature') { | ||||
|         features.add(element.attributes['var']! as String); | ||||
|       } else if (element.tag == 'identity') { | ||||
|         identities.add(Identity( | ||||
|           category: element.attributes['category']! as String, | ||||
|           type: element.attributes['type']! as String, | ||||
|           name: element.attributes['name'] as String?, | ||||
|         ),); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     final result = Result<DiscoError, DiscoInfo>( | ||||
|       DiscoInfo( | ||||
|         features, | ||||
|         identities, | ||||
|         query.findTags('x', xmlns: dataFormsXmlns).map(parseDataForm).toList(), | ||||
|         JID.fromString(stanza.attributes['from']! as String), | ||||
|       DiscoInfo.fromQuery( | ||||
|         query, | ||||
|         JID.fromString(entity), | ||||
|       ), | ||||
|     ); | ||||
|     await _exitDiscoInfoCriticalSection(cacheKey, result); | ||||
| @ -367,8 +340,8 @@ class DiscoManager extends XmppManagerBase { | ||||
|     final query = stanza.firstTag('query'); | ||||
|     if (query == null) return Result(InvalidResponseDiscoError()); | ||||
| 
 | ||||
|     final error = stanza.firstTag('error'); | ||||
|     if (error != null && stanza.type == 'error') { | ||||
|     if (stanza.type == 'error') { | ||||
|       //final error = stanza.firstTag('error'); | ||||
|       //print("Disco Items error: " + error.toXml()); | ||||
|       return Result(ErrorResponseDiscoError()); | ||||
|     } | ||||
|  | ||||
| @ -1,10 +1,18 @@ | ||||
| import 'dart:convert'; | ||||
| import 'package:cryptography/cryptography.dart'; | ||||
| import 'package:meta/meta.dart'; | ||||
| import 'package:moxxmpp/src/managers/base.dart'; | ||||
| import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/namespaces.dart'; | ||||
| import 'package:moxxmpp/src/presence.dart'; | ||||
| import 'package:moxxmpp/src/rfcs/rfc_4790.dart'; | ||||
| import 'package:moxxmpp/src/stringxml.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; | ||||
| import 'package:moxxmpp/src/xeps/xep_0414.dart'; | ||||
| 
 | ||||
| @immutable | ||||
| class CapabilityHashInfo { | ||||
| 
 | ||||
|   const CapabilityHashInfo(this.ver, this.node, this.hash); | ||||
|   final String ver; | ||||
|   final String node; | ||||
| @ -57,3 +65,86 @@ Future<String> calculateCapabilityHash(DiscoInfo info, HashAlgorithm algorithm) | ||||
|    | ||||
|   return base64.encode((await algorithm.hash(utf8.encode(buffer.toString()))).bytes); | ||||
| } | ||||
| 
 | ||||
| /// A manager implementing the advertising of XEP-0115. It responds to the | ||||
| /// disco#info requests on the specified node with the information provided by | ||||
| /// the DiscoManager. | ||||
| /// NOTE: This manager requires that the DiscoManager is also registered. | ||||
| class EntityCapabilitiesManager extends XmppManagerBase { | ||||
|   EntityCapabilitiesManager(this._capabilityHashBase) : super(); | ||||
| 
 | ||||
|   /// The string that is both the node under which we advertise the disco info | ||||
|   /// and the base for the actual node on which we respond to disco#info requests. | ||||
|   final String _capabilityHashBase; | ||||
| 
 | ||||
|   /// The cached capability hash. | ||||
|   String? _capabilityHash; | ||||
| 
 | ||||
|   @override | ||||
|   String getName() => 'EntityCapabilitiesManager'; | ||||
|    | ||||
|   @override | ||||
|   String getId() => entityCapabilitiesManager; | ||||
| 
 | ||||
|   @override | ||||
|   Future<bool> isSupported() async => true; | ||||
| 
 | ||||
|   @override | ||||
|   List<String> getDiscoFeatures() => [ | ||||
|     capsXmlns, | ||||
|   ]; | ||||
| 
 | ||||
|   /// Computes, if required, the capability hash of the data provided by | ||||
|   /// the DiscoManager. | ||||
|   Future<String> getCapabilityHash() async { | ||||
|     _capabilityHash ??= await calculateCapabilityHash( | ||||
|       getAttributes() | ||||
|         .getManagerById<DiscoManager>(discoManager)! | ||||
|         .getDiscoInfo(null), | ||||
|       getHashByName('sha-1')!, | ||||
|     ); | ||||
| 
 | ||||
|     return _capabilityHash!; | ||||
|   } | ||||
| 
 | ||||
|   Future<String> _getNode() async { | ||||
|     final hash = await getCapabilityHash(); | ||||
|     return '$_capabilityHashBase#$hash'; | ||||
|   } | ||||
|    | ||||
|   Future<DiscoInfo> _onInfoQuery() async { | ||||
|     return getAttributes() | ||||
|       .getManagerById<DiscoManager>(discoManager)! | ||||
|       .getDiscoInfo(await _getNode()); | ||||
|   } | ||||
| 
 | ||||
|   Future<List<XMLNode>> _prePresenceSent() async { | ||||
|     return [ | ||||
|       XMLNode.xmlns( | ||||
|         tag: 'c', | ||||
|         xmlns: capsXmlns, | ||||
|         attributes: { | ||||
|           'hash': 'sha-1', | ||||
|           'node': _capabilityHashBase, | ||||
|           'ver': await getCapabilityHash(), | ||||
|         }, | ||||
|       ), | ||||
|     ]; | ||||
|   } | ||||
|    | ||||
|   @override | ||||
|   Future<void> postRegisterCallback() async { | ||||
|     await super.postRegisterCallback(); | ||||
|      | ||||
|     getAttributes().getManagerById<DiscoManager>(discoManager)!.registerInfoCallback( | ||||
|         await _getNode(), | ||||
|         _onInfoQuery, | ||||
|       ); | ||||
| 
 | ||||
|     getAttributes() | ||||
|       .getManagerById<PresenceManager>(presenceManager)! | ||||
|       .registerPreSendCallback( | ||||
|         _prePresenceSent, | ||||
|       ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -9,14 +9,14 @@ import 'package:moxxmpp/src/stanza.dart'; | ||||
| /// Data summarizing the XEP-0461 data. | ||||
| class ReplyData { | ||||
|   const ReplyData({ | ||||
|     required this.to, | ||||
|     required this.id, | ||||
|     this.to, | ||||
|     this.start, | ||||
|     this.end, | ||||
|   }); | ||||
| 
 | ||||
|   /// The bare JID to whom the reply applies to | ||||
|   final String to; | ||||
|   final String? to; | ||||
| 
 | ||||
|   /// The stanza ID of the message that is replied to | ||||
|   final String id; | ||||
| @ -72,6 +72,11 @@ class MessageRepliesManager extends XmppManagerBase { | ||||
|   @override | ||||
|   String getId() => messageRepliesManager; | ||||
| 
 | ||||
|   @override | ||||
|   List<String> getDiscoFeatures() => [ | ||||
|     replyXmlns, | ||||
|   ]; | ||||
|    | ||||
|   @override | ||||
|   List<StanzaHandler> getIncomingStanzaHandlers() => [ | ||||
|     StanzaHandler( | ||||
| @ -90,7 +95,7 @@ class MessageRepliesManager extends XmppManagerBase { | ||||
|   Future<StanzaHandlerData> _onMessage(Stanza stanza, StanzaHandlerData state) async { | ||||
|     final reply = stanza.firstTag('reply', xmlns: replyXmlns)!; | ||||
|     final id = reply.attributes['id']! as String; | ||||
|     final to = reply.attributes['to']! as String; | ||||
|     final to = reply.attributes['to'] as String?; | ||||
|     int? start; | ||||
|     int? end; | ||||
| 
 | ||||
| @ -102,11 +107,13 @@ class MessageRepliesManager extends XmppManagerBase { | ||||
|       end = int.parse(body.attributes['end']! as String); | ||||
|     } | ||||
| 
 | ||||
|     return state.copyWith(reply: ReplyData( | ||||
|     return state.copyWith( | ||||
|       reply: ReplyData( | ||||
|         id: id, | ||||
|         to: to, | ||||
|         start: start, | ||||
|         end: end, | ||||
|     ),); | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										276
									
								
								packages/moxxmpp/moxxmpp.doap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								packages/moxxmpp/moxxmpp.doap
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,276 @@ | ||||
| <?xml version='1.0' encoding='UTF-8'?> | ||||
| <rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' | ||||
|          xmlns='http://usefulinc.com/ns/doap#' | ||||
| 	 xmlns:foaf='http://xmlns.com/foaf/0.1/' | ||||
| 	 xmlns:xmpp='https://linkmauve.fr/ns/xmpp-doap#'> | ||||
|   <Project xml:lang='en'> | ||||
|     <name>moxxmpp</name> | ||||
|     <created>2021-12-26</created> | ||||
|     <homepage rdf:resource='https://codeberg.org/moxxy/moxxmpp'/> | ||||
|     <os>Linux</os> | ||||
|     <os>Windows</os> | ||||
|     <os>macOS</os> | ||||
|     <os>Android</os> | ||||
|     <os>iOS</os> | ||||
| 
 | ||||
|     <programming-language>Dart</programming-language> | ||||
| 
 | ||||
|     <maintainer> | ||||
|       <foaf:Person> | ||||
| 	<foaf:name>Alexander "Polynomdivision"</foaf:name> | ||||
| 	<foaf:homepage rdf:resource="https://blog.polynom.me" /> | ||||
|       </foaf:Person> | ||||
|     </maintainer> | ||||
| 
 | ||||
|     <implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html" /> | ||||
| 
 | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>2.13.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>2.5rc3</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.2</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.24.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Only jabber:x:oob</xmpp:note> | ||||
| 	<xmpp:version>1.5</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Receiving data</xmpp:note> | ||||
| 	<xmpp:version>1.1.4</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>2.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.5.2</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.4.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.3.0</xmpp:version> | ||||
| 	<xmpp:note xml:lang="en">Not plugged into the UI</xmpp:note> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.6</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.0.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Exists only as part of support for XEP-0280</xmpp:note> | ||||
| 	<xmpp:version>1.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0300.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Supports only Sha256, Sha512 and blake2b512</xmpp:note> | ||||
| 	<xmpp:version>1.0.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.2.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Read-only support</xmpp:note> | ||||
| 	<xmpp:version>0.4</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:note xml:lang="en">Write-only support</xmpp:note> | ||||
| 	<xmpp:version>0.3.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>1.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.6.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.1.0</xmpp:version> | ||||
| 	<xmpp:note xml:lang="en">Only handles the success case; not accessible via the App</xmpp:note> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>1.1.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.4.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.8.3</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0420.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>0.4.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0424.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.3.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0444.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.1.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0446.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.2.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0447.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.1.2</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0448.html" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>0.2.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0449.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.1.1</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0461.html" /> | ||||
| 	<xmpp:status>complete</xmpp:status> | ||||
| 	<xmpp:version>0.2.0</xmpp:version> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-extensible-file-thumbnails.md" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>0.2.1</xmpp:version> | ||||
| 	<xmpp:note xml:lang="en">Only Blurhash is implemented</xmpp:note> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|     <implements> | ||||
|       <xmpp:SupportedXep> | ||||
| 	<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md" /> | ||||
| 	<xmpp:status>partial</xmpp:status> | ||||
| 	<xmpp:version>0.0.5</xmpp:version> | ||||
| 	<xmpp:note xml:lang="en">Sending and receiving implemented; cancellation not implemented</xmpp:note> | ||||
|       </xmpp:SupportedXep> | ||||
|     </implements> | ||||
|   </Project> | ||||
| </rdf:RDF> | ||||
							
								
								
									
										43
									
								
								packages/moxxmpp/test/async_queue_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								packages/moxxmpp/test/async_queue_test.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| import 'package:moxxmpp/src/util/queue.dart'; | ||||
| import 'package:test/test.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   test('Test the async queue', () async { | ||||
|     final queue = AsyncQueue(); | ||||
|     int future1Finish = 0; | ||||
|     int future2Finish = 0; | ||||
|     int future3Finish = 0; | ||||
| 
 | ||||
|     await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future1Finish = DateTime.now().millisecondsSinceEpoch)); | ||||
|     await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future2Finish = DateTime.now().millisecondsSinceEpoch)); | ||||
|     await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future3Finish = DateTime.now().millisecondsSinceEpoch)); | ||||
| 
 | ||||
|     await Future<void>.delayed(const Duration(seconds: 12)); | ||||
| 
 | ||||
|     // The three futures must be done | ||||
|     expect(future1Finish != 0, true); | ||||
|     expect(future2Finish != 0, true); | ||||
|     expect(future3Finish != 0, true); | ||||
| 
 | ||||
|     // The end times of the futures must be ordered (on a timeline) | ||||
|     // |-- future1Finish -- future2Finish -- future3Finish --| | ||||
|     expect( | ||||
|       future1Finish < future2Finish && future1Finish < future3Finish, | ||||
|       true, | ||||
|     ); | ||||
|     expect( | ||||
|       future2Finish < future3Finish && future2Finish > future1Finish, | ||||
|       true, | ||||
|     ); | ||||
|     expect( | ||||
|       future3Finish > future1Finish && future3Finish > future2Finish, | ||||
|       true, | ||||
|     ); | ||||
| 
 | ||||
|     // The queue must be empty at the end | ||||
|     expect(queue.queue.isEmpty, true); | ||||
| 
 | ||||
|     // The queue must not be executing anything at the end | ||||
|     expect(queue.isRunning, false); | ||||
|   }); | ||||
| } | ||||
| @ -54,16 +54,20 @@ void main() { | ||||
|     ], | ||||
|   ); | ||||
|    | ||||
|   final connection = XmppConnection(TestingReconnectionPolicy(), stubSocket) | ||||
|     ..registerFeatureNegotiators([ | ||||
|   final connection = XmppConnection( | ||||
|     TestingReconnectionPolicy(), | ||||
|     AlwaysConnectedConnectivityManager(), | ||||
|     stubSocket, | ||||
|   )..registerFeatureNegotiators([ | ||||
|       StubNegotiator1(), | ||||
|       StubNegotiator2(), | ||||
|     ]) | ||||
|     ..registerManagers([ | ||||
|       PresenceManager('http://moxxmpp.example'), | ||||
|       PresenceManager(), | ||||
|       RosterManager(TestingRosterStateManager('', [])), | ||||
|       DiscoManager(), | ||||
|       DiscoManager([]), | ||||
|       PingManager(), | ||||
|       EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|     ]) | ||||
|     ..setConnectionSettings( | ||||
|       ConnectionSettings( | ||||
|  | ||||
| @ -53,7 +53,7 @@ void main() { | ||||
|           ignoreId: true, | ||||
|         ), | ||||
|         StringExpectation( | ||||
|           "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>", | ||||
|           "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>", | ||||
|           '', | ||||
|         ), | ||||
|         StanzaExpectation( | ||||
| @ -65,7 +65,11 @@ void main() { | ||||
| 
 | ||||
|       ], | ||||
|     ); | ||||
|     final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket); | ||||
|     final XmppConnection conn = XmppConnection( | ||||
|       TestingReconnectionPolicy(), | ||||
|       AlwaysConnectedConnectivityManager(), | ||||
|       fakeSocket, | ||||
|     ); | ||||
|     conn.setConnectionSettings(ConnectionSettings( | ||||
|         jid: JID.fromString('polynomdivision@test.server'), | ||||
|         password: 'aaaa', | ||||
| @ -73,10 +77,11 @@ void main() { | ||||
|         allowPlainAuth: true, | ||||
|     ),); | ||||
|     conn.registerManagers([ | ||||
|       PresenceManager('http://moxxmpp.example'), | ||||
|       RosterManager(), | ||||
|       DiscoManager(), | ||||
|       PresenceManager(), | ||||
|       RosterManager(TestingRosterStateManager(null, [])), | ||||
|       DiscoManager([]), | ||||
|       PingManager(), | ||||
|       EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|     ]); | ||||
|     conn.registerFeatureNegotiators( | ||||
|       [ | ||||
| @ -19,6 +19,7 @@ void main() { | ||||
|         ) | ||||
|       ], | ||||
|       [], | ||||
|       null, | ||||
|       JID.fromString('some@user.local/test'), | ||||
|     ); | ||||
| 
 | ||||
| @ -50,6 +51,7 @@ void main() { | ||||
|         ), | ||||
|       ], | ||||
|       [ parseDataForm(XMLNode.fromString(extDiscoDataString)) ], | ||||
|       null, | ||||
|       JID.fromString('some@user.local/test'), | ||||
|     ); | ||||
| 
 | ||||
| @ -158,6 +160,7 @@ void main() { | ||||
|         ) | ||||
|       ], | ||||
|       [], | ||||
|       null, | ||||
|       JID.fromString('user@server.local/test'), | ||||
|     ); | ||||
| 
 | ||||
|  | ||||
| @ -34,7 +34,7 @@ XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { | ||||
|     isFeatureSupported: (_) => false, | ||||
|     getFullJID: () => JID.fromString('hallo@example.server/uwu'), | ||||
|     getSocket: () => StubTCPSocket(play: []), | ||||
|     getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|     getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|     getNegotiatorById: getNegotiatorNullStub, | ||||
|   ); | ||||
| } | ||||
| @ -233,7 +233,11 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -242,12 +246,13 @@ void main() { | ||||
|       ),); | ||||
|       final sm = StreamManagementManager(); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|         PresenceManager(), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|         DiscoManager([]), | ||||
|         PingManager(), | ||||
|         sm, | ||||
|         CarbonsManager()..forceEnable(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -343,7 +348,7 @@ void main() { | ||||
|             '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />', | ||||
|           ), | ||||
|           StringExpectation( | ||||
|             "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>", | ||||
|             "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>", | ||||
|             '<iq type="result" />', | ||||
|           ), | ||||
|           StanzaExpectation( | ||||
| @ -355,7 +360,11 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -364,12 +373,13 @@ void main() { | ||||
|       ),); | ||||
|       final sm = StreamManagementManager(); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           PresenceManager(), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|           DiscoManager([]), | ||||
|           PingManager(), | ||||
|           sm, | ||||
|           CarbonsManager()..forceEnable(), | ||||
|           EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -510,7 +520,11 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -518,9 +532,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           PresenceManager(), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|           DiscoManager([]), | ||||
|           PingManager(), | ||||
|           StreamManagementManager(), | ||||
|       ]); | ||||
| @ -602,7 +616,11 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -610,9 +628,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           PresenceManager(), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|           DiscoManager([]), | ||||
|           PingManager(), | ||||
|           StreamManagementManager(), | ||||
|       ]); | ||||
| @ -694,7 +712,11 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -702,9 +724,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           PresenceManager(), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|           DiscoManager([]), | ||||
|           PingManager(), | ||||
|           StreamManagementManager(), | ||||
|       ]); | ||||
|  | ||||
| @ -22,7 +22,7 @@ void main() { | ||||
|       isFeatureSupported: (_) => false, | ||||
|       getFullJID: () => JID.fromString('bob@xmpp.example/uwu'), | ||||
|       getSocket: () => StubTCPSocket(play: []), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|       getNegotiatorById: getNegotiatorNullStub, | ||||
|     ); | ||||
|     final manager = CarbonsManager(); | ||||
|  | ||||
| @ -50,7 +50,7 @@ void main() { | ||||
|           isFeatureSupported: (_) => false, | ||||
|           getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), | ||||
|           getSocket: () => StubTCPSocket(play: []), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
| @ -79,7 +79,7 @@ void main() { | ||||
|           isFeatureSupported: (_) => false, | ||||
|           getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), | ||||
|           getSocket: () => StubTCPSocket(play: []), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,7 @@ Future<bool> testRosterManager(String bareJid, String resource, String stanzaStr | ||||
|       isFeatureSupported: (_) => false, | ||||
|       getFullJID: () => JID.fromString('$bareJid/$resource'), | ||||
|       getSocket: () => StubTCPSocket(play: []), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|   ),); | ||||
| 
 | ||||
|   final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString)); | ||||
| @ -118,7 +118,10 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       // TODO: This test is broken since we query the server and enable carbons | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -126,11 +129,12 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         PresenceManager(), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager(), | ||||
|         DiscoManager([]), | ||||
|         PingManager(), | ||||
|         StreamManagementManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -172,7 +176,11 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       var receivedEvent = false; | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|         jid: JID.fromString('polynomdivision@test.server'), | ||||
|         password: 'aaaa', | ||||
| @ -180,10 +188,11 @@ void main() { | ||||
|         allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         PresenceManager(), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager(), | ||||
|         DiscoManager([]), | ||||
|         PingManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators([ | ||||
|         SaslPlainNegotiator() | ||||
| @ -226,7 +235,11 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       var receivedEvent = false; | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -234,10 +247,11 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         PresenceManager(), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager(), | ||||
|         DiscoManager([]), | ||||
|         PingManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators([ | ||||
|         SaslPlainNegotiator() | ||||
| @ -326,7 +340,7 @@ void main() { | ||||
|               isFeatureSupported: (_) => false, | ||||
|               getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), | ||||
|               getSocket: () => StubTCPSocket(play: []), | ||||
|               getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|               getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|           ),); | ||||
| 
 | ||||
|           // NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user