Compare commits
	
		
			No commits in common. "7e588f01b0e226a0c6b03f66d5f2b1d055c8715f" and "1cc266c675e7b7ff221ceb6544aca711c00e4569" have entirely different histories.
		
	
	
		
			7e588f01b0
			...
			1cc266c675
		
	
		
| @ -1,7 +1,6 @@ | ||||
| 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,7 +4,6 @@ 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'; | ||||
| @ -95,24 +94,22 @@ 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, | ||||
|       _connectivityManager = connectivityManager { | ||||
|     // Allow the reconnection policy to perform reconnections by itself | ||||
|     _reconnectionPolicy.register( | ||||
|       _attemptReconnection, | ||||
|       _onNetworkConnectionLost, | ||||
|     ); | ||||
|   ) : _reconnectionPolicy = reconnectionPolicy { | ||||
|       // Allow the reconnection policy to perform reconnections by itself | ||||
|       _reconnectionPolicy.register( | ||||
|         _attemptReconnection, | ||||
|         _onNetworkConnectionLost, | ||||
|       ); | ||||
| 
 | ||||
|     _socketStream = _socket.getDataStream(); | ||||
|     // TODO(Unknown): Handle on done | ||||
|     _socketStream.transform(_streamBuffer).forEach(handleXmlStream); | ||||
|     _socket.getEventStream().listen(_handleSocketEvent); | ||||
|       _socketStream = _socket.getDataStream(); | ||||
|       // TODO(Unknown): Handle on done | ||||
|       _socketStream.transform(_streamBuffer).forEach(handleXmlStream); | ||||
|       _socket.getEventStream().listen(_handleSocketEvent); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
| @ -125,16 +122,13 @@ 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(); | ||||
|    | ||||
| @ -223,47 +217,63 @@ class XmppConnection { | ||||
|   /// none can be found. | ||||
|   T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) => _featureNegotiators[id] as T?; | ||||
|    | ||||
|   /// Registers a list of [XmppManagerBase] sub-classes as managers on this connection. | ||||
|   Future<void> registerManagers(List<XmppManagerBase> managers) async { | ||||
|   /// 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 }) { | ||||
|     _log.finest('Registering ${manager.getId()}'); | ||||
|     manager.register( | ||||
|       XmppManagerAttributes( | ||||
|         sendStanza: sendStanza, | ||||
|         sendNonza: sendRawXML, | ||||
|         sendEvent: _sendEvent, | ||||
|         getConnectionSettings: () => _connectionSettings, | ||||
|         getManagerById: getManagerById, | ||||
|         isFeatureSupported: _serverFeatures.contains, | ||||
|         getFullJID: () => _connectionSettings.jid.withResource(_resource), | ||||
|         getSocket: () => _socket, | ||||
|         getConnection: () => this, | ||||
|         getNegotiatorById: getNegotiatorById, | ||||
|       ), | ||||
|     ); | ||||
| 
 | ||||
|     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) { | ||||
|       _log.finest('Registering ${manager.getId()}'); | ||||
|       manager.register( | ||||
|         XmppManagerAttributes( | ||||
|           sendStanza: sendStanza, | ||||
|           sendNonza: sendRawXML, | ||||
|           sendEvent: _sendEvent, | ||||
|           getConnectionSettings: () => _connectionSettings, | ||||
|           getManagerById: getManagerById, | ||||
|           isFeatureSupported: _serverFeatures.contains, | ||||
|           getFullJID: () => _connectionSettings.jid.withResource(_resource), | ||||
|           getSocket: () => _socket, | ||||
|           getConnection: () => this, | ||||
|           getNegotiatorById: getNegotiatorById, | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|       final id = manager.getId(); | ||||
|       _xmppManagers[id] = manager; | ||||
| 
 | ||||
|       _incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers()); | ||||
|       _incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers()); | ||||
|       _outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers()); | ||||
|       _outgoingPostStanzaHandlers.addAll(manager.getOutgoingPostStanzaHandlers()); | ||||
|       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. | ||||
| @ -368,7 +378,7 @@ class XmppConnection { | ||||
|     // Connect again | ||||
|     // ignore: cascade_invocations | ||||
|     _log.finest('Calling connect() from _attemptReconnection'); | ||||
|     await connect(waitForConnection: true); | ||||
|     await connect(); | ||||
|   } | ||||
|    | ||||
|   /// Called when a stream ending error has occurred | ||||
| @ -391,11 +401,7 @@ class XmppConnection { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (await _connectivityManager.hasConnection()) { | ||||
|       await _setConnectionState(XmppConnectionState.error); | ||||
|     } else { | ||||
|       await _setConnectionState(XmppConnectionState.notConnected); | ||||
|     } | ||||
|     await _setConnectionState(XmppConnectionState.error); | ||||
|     await _reconnectionPolicy.onFailure(); | ||||
|   } | ||||
| 
 | ||||
| @ -826,6 +832,7 @@ class XmppConnection { | ||||
|         if (_isMandatoryNegotiationDone(_streamFeatures) && !_isNegotiationPossible(_streamFeatures)) { | ||||
|           _log.finest('Negotiations done!'); | ||||
|           _updateRoutingState(RoutingState.handleStanzas); | ||||
|           await _reconnectionPolicy.onSuccess(); | ||||
|           await _resetIsConnectionRunning(); | ||||
|           await _onNegotiationsDone(); | ||||
|         } else { | ||||
| @ -850,6 +857,7 @@ class XmppConnection { | ||||
|         _log.finest('Negotiations done!'); | ||||
| 
 | ||||
|         _updateRoutingState(RoutingState.handleStanzas); | ||||
|         await _reconnectionPolicy.onSuccess(); | ||||
|         await _resetIsConnectionRunning(); | ||||
|         await _onNegotiationsDone(); | ||||
|       } else { | ||||
| @ -867,6 +875,7 @@ 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; | ||||
| @ -978,7 +987,7 @@ class XmppConnection { | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _disconnect({required XmppConnectionState state, bool triggeredByUser = true}) async { | ||||
|     await _reconnectionPolicy.setShouldReconnect(false); | ||||
|     _reconnectionPolicy.setShouldReconnect(false); | ||||
| 
 | ||||
|     if (triggeredByUser) { | ||||
|       getPresenceManager().sendUnavailablePresence(); | ||||
| @ -1009,21 +1018,17 @@ class XmppConnection { | ||||
|    | ||||
|   /// Like [connect] but the Future resolves when the resource binding is either done or | ||||
|   /// SASL has failed. | ||||
|   Future<XmppConnectionResult> connectAwaitable({ String? lastResource, bool waitForConnection = false }) async { | ||||
|   Future<XmppConnectionResult> connectAwaitable({ String? lastResource }) async { | ||||
|     _runPreConnectionAssertions(); | ||||
|     await _resetIsConnectionRunning(); | ||||
|     _connectionCompleter = Completer(); | ||||
|     _log.finest('Calling connect() from connectAwaitable'); | ||||
|     await connect( | ||||
|       lastResource: lastResource, | ||||
|       waitForConnection: waitForConnection, | ||||
|       shouldReconnect: false, | ||||
|     ); | ||||
|     await connect(lastResource: lastResource); | ||||
|     return _connectionCompleter!.future; | ||||
|   } | ||||
|   | ||||
|   /// Start the connection process using the provided connection settings. | ||||
|   Future<void> connect({ String? lastResource, bool waitForConnection = false, bool shouldReconnect = true }) async { | ||||
|   Future<void> connect({ String? lastResource }) async { | ||||
|     if (_connectionState != XmppConnectionState.notConnected && _connectionState != XmppConnectionState.error) { | ||||
|       _log.fine('Cancelling this connection attempt as one appears to be already running.'); | ||||
|       return; | ||||
| @ -1031,25 +1036,15 @@ 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; | ||||
|  | ||||
| @ -1,18 +0,0 @@ | ||||
| /// 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,21 +1,14 @@ | ||||
| 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; | ||||
| @ -55,9 +48,6 @@ 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(); | ||||
| @ -73,24 +63,6 @@ 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. | ||||
|  | ||||
| @ -1,25 +1,25 @@ | ||||
| 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 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 omemoManager = 'org.moxxmpp.omemomanager'; | ||||
| const emeManager = 'org.moxxmpp.ememanager'; | ||||
| const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager'; | ||||
| @ -28,4 +28,3 @@ 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,19 +8,17 @@ 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() : super(); | ||||
|   PresenceManager(this._capHashNode) : _capabilityHash = null, super(); | ||||
|   String? _capabilityHash; | ||||
|   final String _capHashNode; | ||||
| 
 | ||||
|   /// The list of pre-send callbacks. | ||||
|   final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true); | ||||
|   String get capabilityHashNode => _capHashNode; | ||||
|    | ||||
|   @override | ||||
|   String getId() => presenceManager; | ||||
| @ -41,11 +39,6 @@ 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(); | ||||
| @ -70,26 +63,43 @@ 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 children = List<XMLNode>.from([ | ||||
|       XMLNode( | ||||
|         tag: 'show', | ||||
|         text: 'chat', | ||||
|       ), | ||||
|     ]); | ||||
| 
 | ||||
|     for (final callback in _presenceCallbacks) { | ||||
|       children.addAll( | ||||
|         await callback(), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     final attrs = getAttributes(); | ||||
|     attrs.sendNonza( | ||||
|       Stanza.presence( | ||||
|         from: attrs.getFullJID().toString(), | ||||
|         children: children, | ||||
|         children: [ | ||||
|           XMLNode( | ||||
|             tag: 'show', | ||||
|             text: 'chat', | ||||
|           ), | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'c', | ||||
|             xmlns: capsXmlns, | ||||
|             attributes: { | ||||
|               'hash': 'sha-1', | ||||
|               'node': _capHashNode, | ||||
|               'ver': await getCapabilityHash() | ||||
|             }, | ||||
|           ) | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -2,7 +2,6 @@ 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. | ||||
| @ -25,16 +24,10 @@ abstract class ReconnectionPolicy { | ||||
|   bool _shouldAttemptReconnection = false; | ||||
| 
 | ||||
|   /// Indicate if a reconnection attempt is currently running. | ||||
|   @protected | ||||
|   bool isReconnecting = false; | ||||
|   bool _isReconnecting = false; | ||||
| 
 | ||||
|   /// And the corresponding lock | ||||
|   @protected | ||||
|   final Lock lock = Lock(); | ||||
| 
 | ||||
|   /// The lock for accessing [_shouldAttemptReconnection] | ||||
|   @protected | ||||
|   final Lock shouldReconnectLock = Lock(); | ||||
|   final Lock _isReconnectingLock = Lock(); | ||||
|    | ||||
|   /// Called by XmppConnection to register the policy. | ||||
|   void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) { | ||||
| @ -55,121 +48,96 @@ abstract class ReconnectionPolicy { | ||||
|   /// Caled by the XmppConnection when the reconnection was successful. | ||||
|   Future<void> onSuccess(); | ||||
| 
 | ||||
|   Future<bool> getShouldReconnect() async { | ||||
|     return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection); | ||||
|   } | ||||
|   bool get shouldReconnect => _shouldAttemptReconnection; | ||||
| 
 | ||||
|   /// Set whether a reconnection attempt should be made. | ||||
|   Future<void> setShouldReconnect(bool value) async { | ||||
|     return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection = value); | ||||
|   void setShouldReconnect(bool value) { | ||||
|     _shouldAttemptReconnection = value; | ||||
|   } | ||||
| 
 | ||||
|   /// Returns true if the manager is currently triggering a reconnection. If not, returns | ||||
|   /// false. | ||||
|   Future<bool> isReconnectionRunning() async { | ||||
|     return lock.synchronized(() => isReconnecting); | ||||
|     return _isReconnectingLock.synchronized(() => _isReconnecting); | ||||
|   } | ||||
| 
 | ||||
|   /// Set the isReconnecting state to [value]. | ||||
|   /// Set the _isReconnecting state to [value]. | ||||
|   @protected | ||||
|   Future<void> setIsReconnecting(bool value) async { | ||||
|     await lock.synchronized(() async { | ||||
|       isReconnecting = value; | ||||
|     await _isReconnectingLock.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 RandomBackoffReconnectionPolicy extends ReconnectionPolicy { | ||||
|   RandomBackoffReconnectionPolicy( | ||||
|     this._minBackoffTime, | ||||
|     this._maxBackoffTime, | ||||
|   ) : assert(_minBackoffTime < _maxBackoffTime, '_minBackoffTime must be smaller than _maxBackoffTime'), | ||||
|       super(); | ||||
| class ExponentialBackoffReconnectionPolicy extends ReconnectionPolicy { | ||||
|   ExponentialBackoffReconnectionPolicy(this._maxBackoffTime) : super(); | ||||
| 
 | ||||
|   /// The maximum time in seconds that a backoff should be. | ||||
|   /// The maximum time in seconds that a backoff step should be. | ||||
|   final int _maxBackoffTime; | ||||
| 
 | ||||
|   /// The minimum time in seconds that a backoff should be. | ||||
|   final int _minBackoffTime; | ||||
|   /// Amount of consecutive failed reconnections. | ||||
|   int _counter = 0; | ||||
| 
 | ||||
|   /// Backoff timer. | ||||
|   Timer? _timer; | ||||
| 
 | ||||
|   final Lock _timerLock = Lock(); | ||||
| 
 | ||||
|   /// Logger. | ||||
|   final Logger _log = Logger('RandomBackoffReconnectionPolicy'); | ||||
| 
 | ||||
|   /// Event queue | ||||
|   final AsyncQueue _eventQueue = AsyncQueue(); | ||||
|   final Logger _log = Logger('ExponentialBackoffReconnectionPolicy'); | ||||
| 
 | ||||
|   /// Called when the backoff expired | ||||
|   Future<void> _onTimerElapsed() async { | ||||
|     _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; | ||||
|     final isReconnecting = await isReconnectionRunning(); | ||||
|     if (shouldReconnect) { | ||||
|       if (!isReconnecting) { | ||||
|         await setIsReconnecting(true); | ||||
|         await performReconnect!(); | ||||
|       } else { | ||||
|         // Should never happen. | ||||
|         _log.fine('Backoff timer expired but reconnection is running, so doing nothing.'); | ||||
|       } | ||||
| 
 | ||||
|       if (isReconnecting) { | ||||
|         _log.fine('Backoff timer expired but a reconnection is running, so doing nothing.'); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       _log.fine('Triggering reconnect'); | ||||
|       isReconnecting = true; | ||||
|       await performReconnect!(); | ||||
|     }); | ||||
| 
 | ||||
|     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 { | ||||
|     // ignore: unnecessary_lambdas | ||||
|     await _eventQueue.addJob(() => _reset()); | ||||
|   } | ||||
|     _log.finest('Resetting internal state'); | ||||
|     _counter = 0; | ||||
|     await setIsReconnecting(false); | ||||
| 
 | ||||
|   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; | ||||
|     if (_timer != null) { | ||||
|       _timer!.cancel(); | ||||
|       _timer = null; | ||||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|     // ignore: unnecessary_lambdas | ||||
|     await _eventQueue.addJob(() => _onFailure()); | ||||
|     _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); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|  | ||||
| @ -1,56 +0,0 @@ | ||||
| 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,6 +3,7 @@ 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; | ||||
| @ -22,6 +23,7 @@ class DataFormOption { | ||||
| } | ||||
| 
 | ||||
| class DataFormField { | ||||
| 
 | ||||
|   const DataFormField({ | ||||
|       required this.options, | ||||
|       required this.values, | ||||
| @ -58,6 +60,7 @@ class DataFormField { | ||||
| } | ||||
| 
 | ||||
| class DataForm { | ||||
| 
 | ||||
|   const DataForm({ | ||||
|       required this.type, | ||||
|       required this.instructions, | ||||
|  | ||||
| @ -6,20 +6,20 @@ import 'package:moxxmpp/src/stringxml.dart'; | ||||
| 
 | ||||
| Stanza buildDiscoInfoQueryStanza(String entity, String? node) { | ||||
|   return Stanza.iq(to: entity, type: 'get', children: [ | ||||
|     XMLNode.xmlns( | ||||
|       tag: 'query', | ||||
|       xmlns: discoInfoXmlns, | ||||
|       attributes: node != null ? { 'node': node } : {}, | ||||
|     ) | ||||
|       XMLNode.xmlns( | ||||
|         tag: 'query', | ||||
|         xmlns: discoInfoXmlns, | ||||
|         attributes: node != null ? { 'node': node } : {}, | ||||
|       ) | ||||
|   ],); | ||||
| } | ||||
| 
 | ||||
| Stanza buildDiscoItemsQueryStanza(String entity, { String? node }) { | ||||
|   return Stanza.iq(to: entity, type: 'get', children: [ | ||||
|     XMLNode.xmlns( | ||||
|       tag: 'query', | ||||
|       xmlns: discoItemsXmlns, | ||||
|       attributes: node != null ? { 'node': node } : {}, | ||||
|     ) | ||||
|       XMLNode.xmlns( | ||||
|         tag: 'query', | ||||
|         xmlns: discoItemsXmlns, | ||||
|         attributes: node != null ? { 'node': node } : {}, | ||||
|       ) | ||||
|   ],); | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,9 @@ | ||||
| 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; | ||||
| @ -24,96 +23,24 @@ 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 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()), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
|   final JID jid; | ||||
| } | ||||
| 
 | ||||
| @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,21 +7,17 @@ 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); | ||||
| @ -37,49 +33,33 @@ 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 { | ||||
|   /// [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), | ||||
|   DiscoManager() | ||||
|     : _features = List.empty(growable: true), | ||||
|       _capHashCache = {}, | ||||
|       _capHashInfoCache = {}, | ||||
|       _discoInfoCache = {}, | ||||
|       _runningInfoQueries = {}, | ||||
|       _cacheLock = Lock(), | ||||
|       super(); | ||||
| 
 | ||||
|   /// Our features | ||||
|   final List<String> _features = List.empty(growable: true); | ||||
|   final List<String> _features; | ||||
| 
 | ||||
|   /// 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 = Lock(); | ||||
|   final Lock _cacheLock; | ||||
| 
 | ||||
|   /// 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; | ||||
| 
 | ||||
| @ -125,20 +105,10 @@ 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 addFeatures(List<String> features) { | ||||
|   void addDiscoFeatures(List<String> features) { | ||||
|     for (final feat in features) { | ||||
|       if (!_features.contains(feat)) { | ||||
|         _features.add(feat); | ||||
| @ -146,16 +116,6 @@ 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; | ||||
| @ -186,33 +146,45 @@ class DiscoManager extends XmppManagerBase { | ||||
|     }); | ||||
|   } | ||||
|    | ||||
|   /// 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, | ||||
|     ); | ||||
|   } | ||||
|   /// 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') ]; | ||||
|    | ||||
|   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 (_discoInfoCallbacks.containsKey(node)) { | ||||
|       // We can now assume that node != null | ||||
|       final result = await _discoInfoCallbacks[node]!(); | ||||
|     if (!isCapabilityNode && node != null) { | ||||
|       await reply( | ||||
|         state, | ||||
|         'result', | ||||
|         'error', | ||||
|         [ | ||||
|           result.toXml(), | ||||
|           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, | ||||
|               ) | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
| 
 | ||||
| @ -223,7 +195,24 @@ class DiscoManager extends XmppManagerBase { | ||||
|       state, | ||||
|       'result', | ||||
|       [ | ||||
|         getDiscoInfo(node).toXml(), | ||||
|         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 }, | ||||
|               ); | ||||
|             }), | ||||
|           ], | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
| @ -234,20 +223,30 @@ class DiscoManager extends XmppManagerBase { | ||||
|     if (stanza.type != 'get') return state; | ||||
| 
 | ||||
|     final query = stanza.firstTag('query', xmlns: discoItemsXmlns)!; | ||||
|     final node = query.attributes['node'] as String?; | ||||
|     if (_discoItemsCallbacks.containsKey(node)) { | ||||
|       final result = await _discoItemsCallbacks[node]!(); | ||||
|     if (query.attributes['node'] != null) { | ||||
|       // TODO(Unknown): Handle the node we specified for XEP-0115 | ||||
|       await reply( | ||||
|         state, | ||||
|         'result', | ||||
|         'error', | ||||
|         [ | ||||
|           XMLNode.xmlns( | ||||
|             tag: 'query', | ||||
|             xmlns: discoItemsXmlns, | ||||
|             attributes: <String, String>{ | ||||
|               'node': node!, | ||||
|               'node': query.attributes['node']! as String, | ||||
|             }, | ||||
|             children: result.map((item) => item.toXml()).toList(), | ||||
|           ), | ||||
|           XMLNode( | ||||
|             tag: 'error', | ||||
|             attributes: <String, dynamic>{ | ||||
|               'type': 'cancel' | ||||
|             }, | ||||
|             children: [ | ||||
|               XMLNode.xmlns( | ||||
|                 tag: 'not-allowed', | ||||
|                 xmlns: fullStanzaXmlns, | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
| @ -255,7 +254,18 @@ class DiscoManager extends XmppManagerBase { | ||||
|       return state.copyWith(done: true); | ||||
|     } | ||||
| 
 | ||||
|     return state; | ||||
|     await reply( | ||||
|       state, | ||||
|       'result', | ||||
|       [ | ||||
|         XMLNode.xmlns( | ||||
|           tag: 'query', | ||||
|           xmlns: discoItemsXmlns, | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
| 
 | ||||
|     return state.copyWith(done: true); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async { | ||||
| @ -312,17 +322,34 @@ class DiscoManager extends XmppManagerBase { | ||||
|       return result; | ||||
|     } | ||||
| 
 | ||||
|     if (stanza.attributes['type'] == 'error') { | ||||
|       //final error = stanza.firstTag('error'); | ||||
|     final error = stanza.firstTag('error'); | ||||
|     if (error != null && stanza.attributes['type'] == '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.fromQuery( | ||||
|         query, | ||||
|         JID.fromString(entity), | ||||
|       DiscoInfo( | ||||
|         features, | ||||
|         identities, | ||||
|         query.findTags('x', xmlns: dataFormsXmlns).map(parseDataForm).toList(), | ||||
|         JID.fromString(stanza.attributes['from']! as String), | ||||
|       ), | ||||
|     ); | ||||
|     await _exitDiscoInfoCriticalSection(cacheKey, result); | ||||
| @ -340,8 +367,8 @@ class DiscoManager extends XmppManagerBase { | ||||
|     final query = stanza.firstTag('query'); | ||||
|     if (query == null) return Result(InvalidResponseDiscoError()); | ||||
| 
 | ||||
|     if (stanza.type == 'error') { | ||||
|       //final error = stanza.firstTag('error'); | ||||
|     final error = stanza.firstTag('error'); | ||||
|     if (error != null && stanza.type == 'error') { | ||||
|       //print("Disco Items error: " + error.toXml()); | ||||
|       return Result(ErrorResponseDiscoError()); | ||||
|     } | ||||
|  | ||||
| @ -1,18 +1,10 @@ | ||||
| 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; | ||||
| @ -65,86 +57,3 @@ 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,11 +72,6 @@ class MessageRepliesManager extends XmppManagerBase { | ||||
|   @override | ||||
|   String getId() => messageRepliesManager; | ||||
| 
 | ||||
|   @override | ||||
|   List<String> getDiscoFeatures() => [ | ||||
|     replyXmlns, | ||||
|   ]; | ||||
|    | ||||
|   @override | ||||
|   List<StanzaHandler> getIncomingStanzaHandlers() => [ | ||||
|     StanzaHandler( | ||||
| @ -95,7 +90,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; | ||||
| 
 | ||||
| @ -107,13 +102,11 @@ 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, | ||||
|       ), | ||||
|     ); | ||||
|     ),); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,276 +0,0 @@ | ||||
| <?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> | ||||
| @ -1,43 +0,0 @@ | ||||
| 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,20 +54,16 @@ void main() { | ||||
|     ], | ||||
|   ); | ||||
|    | ||||
|   final connection = XmppConnection( | ||||
|     TestingReconnectionPolicy(), | ||||
|     AlwaysConnectedConnectivityManager(), | ||||
|     stubSocket, | ||||
|   )..registerFeatureNegotiators([ | ||||
|   final connection = XmppConnection(TestingReconnectionPolicy(), stubSocket) | ||||
|     ..registerFeatureNegotiators([ | ||||
|       StubNegotiator1(), | ||||
|       StubNegotiator2(), | ||||
|     ]) | ||||
|     ..registerManagers([ | ||||
|       PresenceManager(), | ||||
|       PresenceManager('http://moxxmpp.example'), | ||||
|       RosterManager(TestingRosterStateManager('', [])), | ||||
|       DiscoManager([]), | ||||
|       DiscoManager(), | ||||
|       PingManager(), | ||||
|       EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|     ]) | ||||
|     ..setConnectionSettings( | ||||
|       ConnectionSettings( | ||||
|  | ||||
| @ -3,14 +3,14 @@ import 'package:test/test.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   test('Parsing', () { | ||||
|     const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; | ||||
|       const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; | ||||
| 
 | ||||
|     final form = parseDataForm(XMLNode.fromString(testData)); | ||||
|     expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo'); | ||||
|     expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]); | ||||
|     expect(form.getFieldByVar('os')?.values.first, 'Mac'); | ||||
|     expect(form.getFieldByVar('os_version')?.values.first, '10.5.1'); | ||||
|     expect(form.getFieldByVar('software')?.values.first, 'Psi'); | ||||
|     expect(form.getFieldByVar('software_version')?.values.first, '0.11'); | ||||
|       final form = parseDataForm(XMLNode.fromString(testData)); | ||||
|       expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo'); | ||||
|       expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]); | ||||
|       expect(form.getFieldByVar('os')?.values.first, 'Mac'); | ||||
|       expect(form.getFieldByVar('os_version')?.values.first, '10.5.1'); | ||||
|       expect(form.getFieldByVar('software')?.values.first, 'Psi'); | ||||
|       expect(form.getFieldByVar('software_version')?.values.first, '0.11'); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -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='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></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='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>", | ||||
|           '', | ||||
|         ), | ||||
|         StanzaExpectation( | ||||
| @ -65,11 +65,7 @@ void main() { | ||||
| 
 | ||||
|       ], | ||||
|     ); | ||||
|     final XmppConnection conn = XmppConnection( | ||||
|       TestingReconnectionPolicy(), | ||||
|       AlwaysConnectedConnectivityManager(), | ||||
|       fakeSocket, | ||||
|     ); | ||||
|     final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket); | ||||
|     conn.setConnectionSettings(ConnectionSettings( | ||||
|         jid: JID.fromString('polynomdivision@test.server'), | ||||
|         password: 'aaaa', | ||||
| @ -77,11 +73,10 @@ void main() { | ||||
|         allowPlainAuth: true, | ||||
|     ),); | ||||
|     conn.registerManagers([ | ||||
|       PresenceManager(), | ||||
|       RosterManager(TestingRosterStateManager(null, [])), | ||||
|       DiscoManager([]), | ||||
|       PresenceManager('http://moxxmpp.example'), | ||||
|       RosterManager(), | ||||
|       DiscoManager(), | ||||
|       PingManager(), | ||||
|       EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|     ]); | ||||
|     conn.registerFeatureNegotiators( | ||||
|       [ | ||||
| @ -4,167 +4,164 @@ import 'package:test/test.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   test('Test XEP example', () async { | ||||
|     final data = DiscoInfo( | ||||
|       [ | ||||
|         'http://jabber.org/protocol/caps', | ||||
|         'http://jabber.org/protocol/disco#info', | ||||
|         'http://jabber.org/protocol/disco#items', | ||||
|         'http://jabber.org/protocol/muc' | ||||
|       ], | ||||
|       [ | ||||
|         Identity( | ||||
|           category: 'client', | ||||
|           type: 'pc', | ||||
|           name: 'Exodus 0.9.1', | ||||
|         ) | ||||
|       ], | ||||
|       [], | ||||
|       null, | ||||
|       JID.fromString('some@user.local/test'), | ||||
|     ); | ||||
|       final data = DiscoInfo( | ||||
|         [ | ||||
|           'http://jabber.org/protocol/caps', | ||||
|           'http://jabber.org/protocol/disco#info', | ||||
|           'http://jabber.org/protocol/disco#items', | ||||
|           'http://jabber.org/protocol/muc' | ||||
|         ], | ||||
|         [ | ||||
|           Identity( | ||||
|             category: 'client', | ||||
|             type: 'pc', | ||||
|             name: 'Exodus 0.9.1', | ||||
|           ) | ||||
|         ], | ||||
|         [], | ||||
|         JID.fromString('some@user.local/test'), | ||||
|       ); | ||||
| 
 | ||||
|     final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|     expect(hash, 'QgayPKawpkPSDYmwT/WM94uAlu0='); | ||||
|       final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|       expect(hash, 'QgayPKawpkPSDYmwT/WM94uAlu0='); | ||||
|   }); | ||||
| 
 | ||||
|   test('Test complex generation example', () async { | ||||
|     const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; | ||||
|     final data = DiscoInfo( | ||||
|       [ | ||||
|         'http://jabber.org/protocol/caps', | ||||
|         'http://jabber.org/protocol/disco#info', | ||||
|         'http://jabber.org/protocol/disco#items', | ||||
|         'http://jabber.org/protocol/muc' | ||||
|       ], | ||||
|       [ | ||||
|         const Identity( | ||||
|           category: 'client', | ||||
|           type: 'pc', | ||||
|           name: 'Psi 0.11', | ||||
|           lang: 'en', | ||||
|         ), | ||||
|         const Identity( | ||||
|           category: 'client', | ||||
|           type: 'pc', | ||||
|           name: 'Ψ 0.11', | ||||
|           lang: 'el', | ||||
|         ), | ||||
|       ], | ||||
|       [ parseDataForm(XMLNode.fromString(extDiscoDataString)) ], | ||||
|       null, | ||||
|       JID.fromString('some@user.local/test'), | ||||
|     ); | ||||
|       const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>"; | ||||
|       final data = DiscoInfo( | ||||
|         [ | ||||
|           'http://jabber.org/protocol/caps', | ||||
|           'http://jabber.org/protocol/disco#info', | ||||
|           'http://jabber.org/protocol/disco#items', | ||||
|           'http://jabber.org/protocol/muc' | ||||
|         ], | ||||
|         [ | ||||
|           const Identity( | ||||
|             category: 'client', | ||||
|             type: 'pc', | ||||
|             name: 'Psi 0.11', | ||||
|             lang: 'en', | ||||
|           ), | ||||
|           const Identity( | ||||
|             category: 'client', | ||||
|             type: 'pc', | ||||
|             name: 'Ψ 0.11', | ||||
|             lang: 'el', | ||||
|           ), | ||||
|         ], | ||||
|         [ parseDataForm(XMLNode.fromString(extDiscoDataString)) ], | ||||
|         JID.fromString('some@user.local/test'), | ||||
|       ); | ||||
| 
 | ||||
|     final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|     expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w='); | ||||
|       final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|       expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w='); | ||||
|   }); | ||||
|    | ||||
|   test('Test Gajim capability hash computation', () async { | ||||
|     // TODO: This one fails | ||||
|     /* | ||||
|     final data = DiscoInfo( | ||||
|       features: [ | ||||
|         "http://jabber.org/protocol/bytestreams", | ||||
|         "http://jabber.org/protocol/muc", | ||||
|         "http://jabber.org/protocol/commands", | ||||
|         "http://jabber.org/protocol/disco#info", | ||||
|         "jabber:iq:last", | ||||
|         "jabber:x:data", | ||||
|         "jabber:x:encrypted", | ||||
|         "urn:xmpp:ping", | ||||
|         "http://jabber.org/protocol/chatstates", | ||||
|         "urn:xmpp:receipts", | ||||
|         "urn:xmpp:time", | ||||
|         "jabber:iq:version", | ||||
|         "http://jabber.org/protocol/rosterx", | ||||
|         "urn:xmpp:sec-label:0", | ||||
|         "jabber:x:conference", | ||||
|         "urn:xmpp:message-correct:0", | ||||
|         "urn:xmpp:chat-markers:0", | ||||
|         "urn:xmpp:eme:0", | ||||
|         "http://jabber.org/protocol/xhtml-im", | ||||
|         "urn:xmpp:hashes:2", | ||||
|         "urn:xmpp:hash-function-text-names:md5", | ||||
|         "urn:xmpp:hash-function-text-names:sha-1", | ||||
|         "urn:xmpp:hash-function-text-names:sha-256", | ||||
|         "urn:xmpp:hash-function-text-names:sha-512", | ||||
|         "urn:xmpp:hash-function-text-names:sha3-256", | ||||
|         "urn:xmpp:hash-function-text-names:sha3-512", | ||||
|         "urn:xmpp:hash-function-text-names:id-blake2b256", | ||||
|         "urn:xmpp:hash-function-text-names:id-blake2b512", | ||||
|         "urn:xmpp:jingle:1", | ||||
|         "urn:xmpp:jingle:apps:file-transfer:5", | ||||
|         "urn:xmpp:jingle:security:xtls:0", | ||||
|         "urn:xmpp:jingle:transports:s5b:1", | ||||
|         "urn:xmpp:jingle:transports:ibb:1", | ||||
|         "urn:xmpp:avatar:metadata+notify", | ||||
|         "urn:xmpp:message-moderate:0", | ||||
|         "http://jabber.org/protocol/tune+notify", | ||||
|         "http://jabber.org/protocol/geoloc+notify", | ||||
|         "http://jabber.org/protocol/nick+notify", | ||||
|         "eu.siacs.conversations.axolotl.devicelist+notify", | ||||
|       ], | ||||
|       identities: [ | ||||
|         Identity( | ||||
|           category: "client", | ||||
|           type: "pc", | ||||
|           name: "Gajim" | ||||
|         ) | ||||
|       ] | ||||
|     ); | ||||
|       // TODO: This one fails | ||||
|       /* | ||||
|       final data = DiscoInfo( | ||||
|         features: [ | ||||
|           "http://jabber.org/protocol/bytestreams", | ||||
|           "http://jabber.org/protocol/muc", | ||||
|           "http://jabber.org/protocol/commands", | ||||
|           "http://jabber.org/protocol/disco#info", | ||||
|           "jabber:iq:last", | ||||
|           "jabber:x:data", | ||||
|           "jabber:x:encrypted", | ||||
|           "urn:xmpp:ping", | ||||
|           "http://jabber.org/protocol/chatstates", | ||||
|           "urn:xmpp:receipts", | ||||
|           "urn:xmpp:time", | ||||
|           "jabber:iq:version", | ||||
|           "http://jabber.org/protocol/rosterx", | ||||
|           "urn:xmpp:sec-label:0", | ||||
|           "jabber:x:conference", | ||||
|           "urn:xmpp:message-correct:0", | ||||
|           "urn:xmpp:chat-markers:0", | ||||
|           "urn:xmpp:eme:0", | ||||
|           "http://jabber.org/protocol/xhtml-im", | ||||
|           "urn:xmpp:hashes:2", | ||||
|           "urn:xmpp:hash-function-text-names:md5", | ||||
|           "urn:xmpp:hash-function-text-names:sha-1", | ||||
|           "urn:xmpp:hash-function-text-names:sha-256", | ||||
|           "urn:xmpp:hash-function-text-names:sha-512", | ||||
|           "urn:xmpp:hash-function-text-names:sha3-256", | ||||
|           "urn:xmpp:hash-function-text-names:sha3-512", | ||||
|           "urn:xmpp:hash-function-text-names:id-blake2b256", | ||||
|           "urn:xmpp:hash-function-text-names:id-blake2b512", | ||||
|           "urn:xmpp:jingle:1", | ||||
|           "urn:xmpp:jingle:apps:file-transfer:5", | ||||
|           "urn:xmpp:jingle:security:xtls:0", | ||||
|           "urn:xmpp:jingle:transports:s5b:1", | ||||
|           "urn:xmpp:jingle:transports:ibb:1", | ||||
|           "urn:xmpp:avatar:metadata+notify", | ||||
|           "urn:xmpp:message-moderate:0", | ||||
|           "http://jabber.org/protocol/tune+notify", | ||||
|           "http://jabber.org/protocol/geoloc+notify", | ||||
|           "http://jabber.org/protocol/nick+notify", | ||||
|           "eu.siacs.conversations.axolotl.devicelist+notify", | ||||
|         ], | ||||
|         identities: [ | ||||
|           Identity( | ||||
|             category: "client", | ||||
|             type: "pc", | ||||
|             name: "Gajim" | ||||
|           ) | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|     final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|     expect(hash, "T7fOZrtBnV8sDA2fFTS59vyOyUs="); | ||||
|     */ | ||||
|       final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|       expect(hash, "T7fOZrtBnV8sDA2fFTS59vyOyUs="); | ||||
|       */ | ||||
|   }); | ||||
| 
 | ||||
|   test('Test Conversations hash computation', () async { | ||||
|     final data = DiscoInfo( | ||||
|       [ | ||||
|         'eu.siacs.conversations.axolotl.devicelist+notify', | ||||
|         'http://jabber.org/protocol/caps', | ||||
|         'http://jabber.org/protocol/chatstates', | ||||
|         'http://jabber.org/protocol/disco#info', | ||||
|         'http://jabber.org/protocol/muc', | ||||
|         'http://jabber.org/protocol/nick+notify', | ||||
|         'jabber:iq:version', | ||||
|         'jabber:x:conference', | ||||
|         'jabber:x:oob', | ||||
|         'storage:bookmarks+notify', | ||||
|         'urn:xmpp:avatar:metadata+notify', | ||||
|         'urn:xmpp:chat-markers:0', | ||||
|         'urn:xmpp:jingle-message:0', | ||||
|         'urn:xmpp:jingle:1', | ||||
|         'urn:xmpp:jingle:apps:dtls:0', | ||||
|         'urn:xmpp:jingle:apps:file-transfer:3', | ||||
|         'urn:xmpp:jingle:apps:file-transfer:4', | ||||
|         'urn:xmpp:jingle:apps:file-transfer:5', | ||||
|         'urn:xmpp:jingle:apps:rtp:1', | ||||
|         'urn:xmpp:jingle:apps:rtp:audio', | ||||
|         'urn:xmpp:jingle:apps:rtp:video', | ||||
|         'urn:xmpp:jingle:jet-omemo:0', | ||||
|         'urn:xmpp:jingle:jet:0', | ||||
|         'urn:xmpp:jingle:transports:ibb:1', | ||||
|         'urn:xmpp:jingle:transports:ice-udp:1', | ||||
|         'urn:xmpp:jingle:transports:s5b:1', | ||||
|         'urn:xmpp:message-correct:0', | ||||
|         'urn:xmpp:ping', | ||||
|         'urn:xmpp:receipts', | ||||
|         'urn:xmpp:time' | ||||
|       ], | ||||
|       [ | ||||
|         Identity( | ||||
|           category: 'client', | ||||
|           type: 'phone', | ||||
|           name: 'Conversations', | ||||
|         ) | ||||
|       ], | ||||
|       [], | ||||
|       null, | ||||
|       JID.fromString('user@server.local/test'), | ||||
|     ); | ||||
|       final data = DiscoInfo( | ||||
|         [ | ||||
|           'eu.siacs.conversations.axolotl.devicelist+notify', | ||||
|           'http://jabber.org/protocol/caps', | ||||
|           'http://jabber.org/protocol/chatstates', | ||||
|           'http://jabber.org/protocol/disco#info', | ||||
|           'http://jabber.org/protocol/muc', | ||||
|           'http://jabber.org/protocol/nick+notify', | ||||
|           'jabber:iq:version', | ||||
|           'jabber:x:conference', | ||||
|           'jabber:x:oob', | ||||
|           'storage:bookmarks+notify', | ||||
|           'urn:xmpp:avatar:metadata+notify', | ||||
|           'urn:xmpp:chat-markers:0', | ||||
|           'urn:xmpp:jingle-message:0', | ||||
|           'urn:xmpp:jingle:1', | ||||
|           'urn:xmpp:jingle:apps:dtls:0', | ||||
|           'urn:xmpp:jingle:apps:file-transfer:3', | ||||
|           'urn:xmpp:jingle:apps:file-transfer:4', | ||||
|           'urn:xmpp:jingle:apps:file-transfer:5', | ||||
|           'urn:xmpp:jingle:apps:rtp:1', | ||||
|           'urn:xmpp:jingle:apps:rtp:audio', | ||||
|           'urn:xmpp:jingle:apps:rtp:video', | ||||
|           'urn:xmpp:jingle:jet-omemo:0', | ||||
|           'urn:xmpp:jingle:jet:0', | ||||
|           'urn:xmpp:jingle:transports:ibb:1', | ||||
|           'urn:xmpp:jingle:transports:ice-udp:1', | ||||
|           'urn:xmpp:jingle:transports:s5b:1', | ||||
|           'urn:xmpp:message-correct:0', | ||||
|           'urn:xmpp:ping', | ||||
|           'urn:xmpp:receipts', | ||||
|           'urn:xmpp:time' | ||||
|         ], | ||||
|         [ | ||||
|           Identity( | ||||
|             category: 'client', | ||||
|             type: 'phone', | ||||
|             name: 'Conversations', | ||||
|           ) | ||||
|         ], | ||||
|         [], | ||||
|         JID.fromString('user@server.local/test'), | ||||
|       ); | ||||
| 
 | ||||
|     final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|     expect(hash, 'zcIke+Rk13ah4d1pwDG7bEZsVwA='); | ||||
|       final hash = await calculateCapabilityHash(data, Sha1()); | ||||
|       expect(hash, 'zcIke+Rk13ah4d1pwDG7bEZsVwA='); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -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(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|     getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|     getNegotiatorById: getNegotiatorNullStub, | ||||
|   ); | ||||
| } | ||||
| @ -233,11 +233,7 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -246,13 +242,12 @@ void main() { | ||||
|       ),); | ||||
|       final sm = StreamManagementManager(); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager(), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager([]), | ||||
|         PingManager(), | ||||
|         sm, | ||||
|         CarbonsManager()..forceEnable(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager(), | ||||
|           PingManager(), | ||||
|           sm, | ||||
|           CarbonsManager()..forceEnable(), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -348,7 +343,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='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></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='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>", | ||||
|             '<iq type="result" />', | ||||
|           ), | ||||
|           StanzaExpectation( | ||||
| @ -360,11 +355,7 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -373,13 +364,12 @@ void main() { | ||||
|       ),); | ||||
|       final sm = StreamManagementManager(); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager(), | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager([]), | ||||
|           DiscoManager(), | ||||
|           PingManager(), | ||||
|           sm, | ||||
|           CarbonsManager()..forceEnable(), | ||||
|           EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -520,11 +510,7 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -532,9 +518,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager(), | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager([]), | ||||
|           DiscoManager(), | ||||
|           PingManager(), | ||||
|           StreamManagementManager(), | ||||
|       ]); | ||||
| @ -616,11 +602,7 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -628,9 +610,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager(), | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           RosterManager(TestingRosterStateManager('', [])), | ||||
|           DiscoManager([]), | ||||
|           DiscoManager(), | ||||
|           PingManager(), | ||||
|           StreamManagementManager(), | ||||
|       ]); | ||||
| @ -712,11 +694,7 @@ void main() { | ||||
|         ] | ||||
|       ); | ||||
| 
 | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -724,9 +702,9 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|           PresenceManager(), | ||||
|           PresenceManager('http://moxxmpp.example'), | ||||
|           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(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), 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(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
| @ -79,7 +79,7 @@ void main() { | ||||
|           isFeatureSupported: (_) => false, | ||||
|           getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), | ||||
|           getSocket: () => StubTCPSocket(play: []), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|           getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|         ), | ||||
|       ); | ||||
| 
 | ||||
|  | ||||
| @ -3,52 +3,52 @@ import 'package:test/test.dart'; | ||||
| 
 | ||||
| void main() { | ||||
|   group('Test the XEP-0363 header preparation', () { | ||||
|     test('invariance', () { | ||||
|       final headers = { | ||||
|         'authorization': 'Basic Base64String==', | ||||
|         'cookie': 'foo=bar; user=romeo' | ||||
|       }; | ||||
|       expect( | ||||
|         prepareHeaders(headers), | ||||
|         headers, | ||||
|       ); | ||||
|     }); | ||||
|     test('invariance through uppercase', () { | ||||
|       final headers = { | ||||
|         'Authorization': 'Basic Base64String==', | ||||
|         'Cookie': 'foo=bar; user=romeo' | ||||
|       }; | ||||
|       expect( | ||||
|         prepareHeaders(headers), | ||||
|         headers, | ||||
|       ); | ||||
|     }); | ||||
|     test('remove unspecified headers', () { | ||||
|       final headers = { | ||||
|         'Authorization': 'Basic Base64String==', | ||||
|         'Cookie': 'foo=bar; user=romeo', | ||||
|         'X-Tracking': 'Base64String==' | ||||
|       }; | ||||
|       expect( | ||||
|         prepareHeaders(headers), | ||||
|         { | ||||
|           'Authorization': 'Basic Base64String==', | ||||
|           'Cookie': 'foo=bar; user=romeo', | ||||
|         } | ||||
|       ); | ||||
|     }); | ||||
|     test('remove newlines', () { | ||||
|       final headers = { | ||||
|         'Authorization': '\n\nBasic Base64String==\n\n', | ||||
|         '\nCookie\r\n': 'foo=bar; user=romeo', | ||||
|       }; | ||||
|       expect( | ||||
|         prepareHeaders(headers), | ||||
|         { | ||||
|           'Authorization': 'Basic Base64String==', | ||||
|           'Cookie': 'foo=bar; user=romeo', | ||||
|         } | ||||
|       ); | ||||
|     }); | ||||
|       test('invariance', () { | ||||
|           final headers = { | ||||
|             'authorization': 'Basic Base64String==', | ||||
|             'cookie': 'foo=bar; user=romeo' | ||||
|           }; | ||||
|           expect( | ||||
|             prepareHeaders(headers), | ||||
|             headers, | ||||
|           ); | ||||
|       }); | ||||
|       test('invariance through uppercase', () { | ||||
|           final headers = { | ||||
|             'Authorization': 'Basic Base64String==', | ||||
|             'Cookie': 'foo=bar; user=romeo' | ||||
|           }; | ||||
|           expect( | ||||
|             prepareHeaders(headers), | ||||
|             headers, | ||||
|           ); | ||||
|       }); | ||||
|       test('remove unspecified headers', () { | ||||
|           final headers = { | ||||
|             'Authorization': 'Basic Base64String==', | ||||
|             'Cookie': 'foo=bar; user=romeo', | ||||
|             'X-Tracking': 'Base64String==' | ||||
|           }; | ||||
|           expect( | ||||
|             prepareHeaders(headers), | ||||
|             { | ||||
|               'Authorization': 'Basic Base64String==', | ||||
|               'Cookie': 'foo=bar; user=romeo', | ||||
|             } | ||||
|           ); | ||||
|       }); | ||||
|       test('remove newlines', () { | ||||
|           final headers = { | ||||
|             'Authorization': '\n\nBasic Base64String==\n\n', | ||||
|             '\nCookie\r\n': 'foo=bar; user=romeo', | ||||
|           }; | ||||
|           expect( | ||||
|             prepareHeaders(headers), | ||||
|             { | ||||
|               'Authorization': 'Basic Base64String==', | ||||
|               'Cookie': 'foo=bar; user=romeo', | ||||
|             } | ||||
|           ); | ||||
|       }); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -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(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|       getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|   ),); | ||||
| 
 | ||||
|   final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString)); | ||||
| @ -118,10 +118,7 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       // TODO: This test is broken since we query the server and enable carbons | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -129,12 +126,11 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager(), | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager([]), | ||||
|         DiscoManager(), | ||||
|         PingManager(), | ||||
|         StreamManagementManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators( | ||||
|         [ | ||||
| @ -176,11 +172,7 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       var receivedEvent = false; | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|         jid: JID.fromString('polynomdivision@test.server'), | ||||
|         password: 'aaaa', | ||||
| @ -188,11 +180,10 @@ void main() { | ||||
|         allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager(), | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager([]), | ||||
|         DiscoManager(), | ||||
|         PingManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators([ | ||||
|         SaslPlainNegotiator() | ||||
| @ -235,11 +226,7 @@ void main() { | ||||
|         ], | ||||
|       ); | ||||
|       var receivedEvent = false; | ||||
|       final XmppConnection conn = XmppConnection( | ||||
|         TestingReconnectionPolicy(), | ||||
|         AlwaysConnectedConnectivityManager(), | ||||
|         fakeSocket, | ||||
|       ); | ||||
|       final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket); | ||||
|       conn.setConnectionSettings(ConnectionSettings( | ||||
|           jid: JID.fromString('polynomdivision@test.server'), | ||||
|           password: 'aaaa', | ||||
| @ -247,11 +234,10 @@ void main() { | ||||
|           allowPlainAuth: true, | ||||
|       ),); | ||||
|       conn.registerManagers([ | ||||
|         PresenceManager(), | ||||
|         PresenceManager('http://moxxmpp.example'), | ||||
|         RosterManager(TestingRosterStateManager('', [])), | ||||
|         DiscoManager([]), | ||||
|         DiscoManager(), | ||||
|         PingManager(), | ||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), | ||||
|       ]); | ||||
|       conn.registerFeatureNegotiators([ | ||||
|         SaslPlainNegotiator() | ||||
| @ -340,7 +326,7 @@ void main() { | ||||
|               isFeatureSupported: (_) => false, | ||||
|               getFullJID: () => JID.fromString('some.user@example.server/aaaaa'), | ||||
|               getSocket: () => StubTCPSocket(play: []), | ||||
|               getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])), | ||||
|               getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])), | ||||
|           ),); | ||||
| 
 | ||||
|           // NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user