Compare commits
	
		
			6 Commits
		
	
	
		
			b1da6e5a53
			...
			c3be199cca
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c3be199cca | |||
| 83ebe58c47 | |||
| 4db0ef6b34 | |||
| b95e75329d | |||
| 3163101f82 | |||
| bd4e1d28ea | 
							
								
								
									
										2
									
								
								.gitlint
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.gitlint
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ line-length=72 | |||||||
| [title-trailing-punctuation] | [title-trailing-punctuation] | ||||||
| [title-hard-tab] | [title-hard-tab] | ||||||
| [title-match-regex] | [title-match-regex] | ||||||
| regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$ | regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all)+(,(meta|tests|style|docs|xep|core|example|all))*\)|release): [A-Z0-9].*$ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| [body-trailing-whitespace] | [body-trailing-whitespace] | ||||||
|  | |||||||
| @ -9,6 +9,8 @@ | |||||||
| - **BREAKING**: Remove `DiscoManager.discoInfoCapHashQuery`. | - **BREAKING**: Remove `DiscoManager.discoInfoCapHashQuery`. | ||||||
| - **BREAKING**: The entity argument of `DiscoManager.discoInfoQuery` and `DiscoManager.discoItemsQuery` are now `JID` instead of `String`. | - **BREAKING**: The entity argument of `DiscoManager.discoInfoQuery` and `DiscoManager.discoItemsQuery` are now `JID` instead of `String`. | ||||||
| - **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`. | - **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`. | ||||||
|  | - **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument. | ||||||
|  | - Sent stanzas are not kept in a queue until sent. | ||||||
| 
 | 
 | ||||||
| ## 0.3.1 | ## 0.3.1 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ import 'package:moxxmpp/src/socket.dart'; | |||||||
| import 'package:moxxmpp/src/stanza.dart'; | import 'package:moxxmpp/src/stanza.dart'; | ||||||
| import 'package:moxxmpp/src/stringxml.dart'; | import 'package:moxxmpp/src/stringxml.dart'; | ||||||
| import 'package:moxxmpp/src/types/result.dart'; | import 'package:moxxmpp/src/types/result.dart'; | ||||||
|  | import 'package:moxxmpp/src/util/queue.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; | import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; | import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0352.dart'; | import 'package:moxxmpp/src/xeps/xep_0352.dart'; | ||||||
| @ -48,18 +49,6 @@ enum XmppConnectionState { | |||||||
|   error |   error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Metadata for [XmppConnection.sendStanza]. |  | ||||||
| enum StanzaFromType { |  | ||||||
|   /// Add the full JID to the stanza as the from attribute |  | ||||||
|   full, |  | ||||||
| 
 |  | ||||||
|   /// Add the bare JID to the stanza as the from attribute |  | ||||||
|   bare, |  | ||||||
| 
 |  | ||||||
|   /// Add no JID as the from attribute |  | ||||||
|   none, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /// This class is a connection to the server. | /// This class is a connection to the server. | ||||||
| class XmppConnection { | class XmppConnection { | ||||||
|   XmppConnection( |   XmppConnection( | ||||||
| @ -91,7 +80,12 @@ class XmppConnection { | |||||||
|     _socketStream = _socket.getDataStream(); |     _socketStream = _socket.getDataStream(); | ||||||
|     // TODO(Unknown): Handle on done |     // TODO(Unknown): Handle on done | ||||||
|     _socketStream.transform(_streamParser).forEach(handleXmlStream); |     _socketStream.transform(_streamParser).forEach(handleXmlStream); | ||||||
|     _socket.getEventStream().listen(_handleSocketEvent); |     _socket.getEventStream().listen(handleSocketEvent); | ||||||
|  | 
 | ||||||
|  |     _stanzaQueue = AsyncStanzaQueue( | ||||||
|  |       _sendStanzaImpl, | ||||||
|  |       _canSendData, | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// The state that the connection is currently in |   /// The state that the connection is currently in | ||||||
| @ -175,6 +169,8 @@ class XmppConnection { | |||||||
| 
 | 
 | ||||||
|   bool get isAuthenticated => _isAuthenticated; |   bool get isAuthenticated => _isAuthenticated; | ||||||
| 
 | 
 | ||||||
|  |   late final AsyncStanzaQueue _stanzaQueue; | ||||||
|  | 
 | ||||||
|   /// Returns the JID we authenticate with and add the resource that we have bound. |   /// Returns the JID we authenticate with and add the resource that we have bound. | ||||||
|   JID _getJidWithResource() { |   JID _getJidWithResource() { | ||||||
|     assert(_resource.isNotEmpty, 'The resource must not be empty'); |     assert(_resource.isNotEmpty, 'The resource must not be empty'); | ||||||
| @ -366,7 +362,8 @@ class XmppConnection { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Called whenever the socket creates an event |   /// Called whenever the socket creates an event | ||||||
|   Future<void> _handleSocketEvent(XmppSocketEvent event) async { |   @visibleForTesting | ||||||
|  |   Future<void> handleSocketEvent(XmppSocketEvent event) async { | ||||||
|     if (event is XmppSocketErrorEvent) { |     if (event is XmppSocketErrorEvent) { | ||||||
|       await handleError(SocketError(event)); |       await handleError(SocketError(event)); | ||||||
|     } else if (event is XmppSocketClosureEvent) { |     } else if (event is XmppSocketClosureEvent) { | ||||||
| @ -412,133 +409,135 @@ class XmppConnection { | |||||||
|         .contains(await getConnectionState()); |         .contains(await getConnectionState()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Sends a [stanza] to the server. If stream management is enabled, then keeping track |   /// Sends a stanza described by [details] to the server. Until sent, the stanza is | ||||||
|   /// of the stanza is taken care of. Returns a Future that resolves when we receive a |   /// kept in a queue, that is flushed after going online again. If Stream Management | ||||||
|   /// response to the stanza. |   /// is active, stanza's acknowledgement is tracked. | ||||||
|   /// |  | ||||||
|   /// If addFrom is true, then a 'from' attribute will be added to the stanza if |  | ||||||
|   /// [stanza] has none. |  | ||||||
|   /// If addId is true, then an 'id' attribute will be added to the stanza if [stanza] has |  | ||||||
|   /// none. |  | ||||||
|   // TODO(Unknown): if addId = false, the function crashes. |   // TODO(Unknown): if addId = false, the function crashes. | ||||||
|   Future<XMLNode> sendStanza( |   Future<XMLNode?> sendStanza(StanzaDetails details) async { | ||||||
|     Stanza stanza, { |  | ||||||
|     StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|     bool addId = true, |  | ||||||
|     bool awaitable = true, |  | ||||||
|     bool encrypted = false, |  | ||||||
|     bool forceEncryption = false, |  | ||||||
|   }) async { |  | ||||||
|     assert( |     assert( | ||||||
|       implies(addId == false && stanza.id == null, !awaitable), |       implies( | ||||||
|       'Cannot await a stanza with no id', |         details.awaitable, | ||||||
|  |         details.stanza.id != null && details.stanza.id!.isNotEmpty || | ||||||
|  |             details.addId, | ||||||
|  |       ), | ||||||
|  |       'An awaitable stanza must have an id', | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     // Add extra data in case it was not set |     final completer = details.awaitable ? Completer<XMLNode>() : null; | ||||||
|     var stanza_ = stanza; |     await _stanzaQueue.enqueueStanza( | ||||||
|     if (addId && (stanza_.id == null || stanza_.id == '')) { |       StanzaQueueEntry( | ||||||
|       stanza_ = stanza.copyWith(id: generateId()); |         details, | ||||||
|  |         completer, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     return completer?.future; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   Future<void> _sendStanzaImpl(StanzaQueueEntry entry) async { | ||||||
|  |     final details = entry.details; | ||||||
|  |     var newStanza = details.stanza; | ||||||
|  | 
 | ||||||
|  |     // Generate an id, if requested | ||||||
|  |     if (details.addId && (newStanza.id == null || newStanza.id == '')) { | ||||||
|  |       newStanza = newStanza.copyWith(id: generateId()); | ||||||
|     } |     } | ||||||
|     if (addFrom != StanzaFromType.none && | 
 | ||||||
|         (stanza_.from == null || stanza_.from == '')) { |     // NOTE: Originally, we handled adding a "from" attribute to the stanza here. | ||||||
|       switch (addFrom) { |     //       However, this is not neccessary as RFC 6120 states: | ||||||
|         case StanzaFromType.full: |     // | ||||||
|           { |     //       > When a server receives an XML stanza from a connected client, the | ||||||
|             stanza_ = stanza_.copyWith( |     //       > server MUST add a 'from' attribute to the stanza or override the | ||||||
|               from: _getJidWithResource().toString(), |     //       > 'from' attribute specified by the client, where the value of the | ||||||
|             ); |     //       > 'from' attribute MUST be the full JID | ||||||
|           } |     //       > (<localpart@domainpart/resource>) determined by the server for | ||||||
|           break; |     //       > the connected resource that generated the stanza (see | ||||||
|         case StanzaFromType.bare: |     //       > Section 4.3.6), or the bare JID (<localpart@domainpart>) in the | ||||||
|           { |     //       > case of subscription-related presence stanzas (see [XMPP-IM]). | ||||||
|             stanza_ = stanza_.copyWith( |     // | ||||||
|               from: connectionSettings.jid.toBare().toString(), |     //       This means that even if we add a "from" attribute, the server will discard | ||||||
|             ); |     //       it. If we don't specify it, then the server will add the correct value | ||||||
|           } |     //       itself. | ||||||
|           break; | 
 | ||||||
|         case StanzaFromType.none: |     // Add the correct stanza namespace | ||||||
|           break; |     newStanza = newStanza.copyWith( | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     stanza_ = stanza_.copyWith( |  | ||||||
|       xmlns: _negotiationsHandler.getStanzaNamespace(), |       xmlns: _negotiationsHandler.getStanzaNamespace(), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     // Run pre-send handlers | ||||||
|     _log.fine('Running pre stanza handlers..'); |     _log.fine('Running pre stanza handlers..'); | ||||||
|     final data = await _runOutgoingPreStanzaHandlers( |     final data = await _runOutgoingPreStanzaHandlers( | ||||||
|       stanza_, |       newStanza, | ||||||
|       initial: StanzaHandlerData( |       initial: StanzaHandlerData( | ||||||
|         false, |         false, | ||||||
|         false, |         false, | ||||||
|         null, |         null, | ||||||
|         stanza_, |         newStanza, | ||||||
|         encrypted: encrypted, |         encrypted: details.encrypted, | ||||||
|         forceEncryption: forceEncryption, |         forceEncryption: details.forceEncryption, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|     _log.fine('Done'); |     _log.fine('Done'); | ||||||
| 
 | 
 | ||||||
|  |     // Cancel sending, if the pre-send handlers indicated it. | ||||||
|     if (data.cancel) { |     if (data.cancel) { | ||||||
|       _log.fine('A stanza handler indicated that it wants to cancel sending.'); |       _log.fine('A stanza handler indicated that it wants to cancel sending.'); | ||||||
|       await _sendEvent(StanzaSendingCancelledEvent(data)); |       await _sendEvent(StanzaSendingCancelledEvent(data)); | ||||||
|       return Stanza( | 
 | ||||||
|         tag: data.stanza.tag, |       // Resolve the future, if one was given. | ||||||
|         to: data.stanza.from, |       if (details.awaitable) { | ||||||
|         from: data.stanza.to, |         entry.completer!.complete( | ||||||
|         attributes: <String, String>{ |           Stanza( | ||||||
|           'type': 'error', |             tag: data.stanza.tag, | ||||||
|           ...data.stanza.id != null |             to: data.stanza.from, | ||||||
|               ? { |             from: data.stanza.to, | ||||||
|                   'id': data.stanza.id!, |             attributes: <String, String>{ | ||||||
|                 } |               'type': 'error', | ||||||
|               : {}, |               if (data.stanza.id != null) 'id': data.stanza.id!, | ||||||
|         }, |             }, | ||||||
|       ); |           ), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Log the (raw) stanza | ||||||
|     final prefix = data.encrypted ? '(Encrypted) ' : ''; |     final prefix = data.encrypted ? '(Encrypted) ' : ''; | ||||||
|     _log.finest('==> $prefix${stanza_.toXml()}'); |     _log.finest('==> $prefix${newStanza.toXml()}'); | ||||||
| 
 | 
 | ||||||
|     final stanzaString = data.stanza.toXml(); |     if (details.awaitable) { | ||||||
| 
 |       await _stanzaAwaiter | ||||||
|     // ignore: cascade_invocations |           .addPending( | ||||||
|     _log.fine('Attempting to acquire lock for ${data.stanza.id}...'); |  | ||||||
|     // TODO(PapaTutuWawa): Handle this much more graceful |  | ||||||
|     var future = Future.value(XMLNode(tag: 'not-used')); |  | ||||||
|     if (awaitable) { |  | ||||||
|       future = await _stanzaAwaiter.addPending( |  | ||||||
|         // A stanza with no to attribute is for direct processing by the server. As such, |         // A stanza with no to attribute is for direct processing by the server. As such, | ||||||
|         // we can correlate it by just *assuming* we have that attribute |         // we can correlate it by just *assuming* we have that attribute | ||||||
|         // (RFC 6120 Section 8.1.1.1) |         // (RFC 6120 Section 8.1.1.1) | ||||||
|         data.stanza.to ?? connectionSettings.jid.toBare().toString(), |         data.stanza.to ?? connectionSettings.jid.toBare().toString(), | ||||||
|         data.stanza.id!, |         data.stanza.id!, | ||||||
|         data.stanza.tag, |         data.stanza.tag, | ||||||
|       ); |       ) | ||||||
|  |           .then((result) { | ||||||
|  |         entry.completer!.complete(result); | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // This uses the StreamManager to behave like a send queue |  | ||||||
|     if (await _canSendData()) { |     if (await _canSendData()) { | ||||||
|       _socket.write(stanzaString); |       _socket.write(data.stanza.toXml()); | ||||||
| 
 |  | ||||||
|       // Try to ack every stanza |  | ||||||
|       // NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent |  | ||||||
|     } else { |     } else { | ||||||
|       _log.fine('_canSendData() returned false.'); |       _log.fine('Not sending dat as _canSendData() returned false.'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Run post-send handlers | ||||||
|     _log.fine('Running post stanza handlers..'); |     _log.fine('Running post stanza handlers..'); | ||||||
|     await _runOutgoingPostStanzaHandlers( |     await _runOutgoingPostStanzaHandlers( | ||||||
|       stanza_, |       newStanza, | ||||||
|       initial: StanzaHandlerData( |       initial: StanzaHandlerData( | ||||||
|         false, |         false, | ||||||
|         false, |         false, | ||||||
|         null, |         null, | ||||||
|         stanza_, |         newStanza, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|     _log.fine('Done'); |     _log.fine('Done'); | ||||||
| 
 |  | ||||||
|     return future; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Called when we timeout during connecting |   /// Called when we timeout during connecting | ||||||
| @ -562,18 +561,11 @@ class XmppConnection { | |||||||
|     // Set the new routing state |     // Set the new routing state | ||||||
|     _updateRoutingState(RoutingState.handleStanzas); |     _updateRoutingState(RoutingState.handleStanzas); | ||||||
| 
 | 
 | ||||||
|     // Set the connection state |  | ||||||
|     await _setConnectionState(XmppConnectionState.connected); |  | ||||||
| 
 |  | ||||||
|     // Enable reconnections |     // Enable reconnections | ||||||
|     if (_enableReconnectOnSuccess) { |     if (_enableReconnectOnSuccess) { | ||||||
|       await _reconnectionPolicy.setShouldReconnect(true); |       await _reconnectionPolicy.setShouldReconnect(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Resolve the connection completion future |  | ||||||
|     _connectionCompleter?.complete(const Result(true)); |  | ||||||
|     _connectionCompleter = null; |  | ||||||
| 
 |  | ||||||
|     // Tell consumers of the event stream that we're done with stream feature |     // Tell consumers of the event stream that we're done with stream feature | ||||||
|     // negotiations |     // negotiations | ||||||
|     await _sendEvent( |     await _sendEvent( | ||||||
| @ -582,6 +574,16 @@ class XmppConnection { | |||||||
|             false, |             false, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  | 
 | ||||||
|  |     // Set the connection state | ||||||
|  |     await _setConnectionState(XmppConnectionState.connected); | ||||||
|  | 
 | ||||||
|  |     // Resolve the connection completion future | ||||||
|  |     _connectionCompleter?.complete(const Result(true)); | ||||||
|  |     _connectionCompleter = null; | ||||||
|  | 
 | ||||||
|  |     // Flush the stanza send queue | ||||||
|  |     await _stanzaQueue.restart(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Sets the connection state to [state] and triggers an event of type |   /// Sets the connection state to [state] and triggers an event of type | ||||||
|  | |||||||
| @ -23,9 +23,11 @@ Future<void> handleUnhandledStanza( | |||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     await conn.sendStanza( |     await conn.sendStanza( | ||||||
|       stanza, |       StanzaDetails( | ||||||
|       awaitable: false, |         stanza, | ||||||
|       forceEncryption: data.encrypted, |         awaitable: false, | ||||||
|  |         forceEncryption: data.encrypted, | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -23,14 +23,7 @@ class XmppManagerAttributes { | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   /// Send a stanza whose response can be awaited. |   /// Send a stanza whose response can be awaited. | ||||||
|   final Future<XMLNode> Function( |   final Future<XMLNode?> Function(StanzaDetails) sendStanza; | ||||||
|     Stanza stanza, { |  | ||||||
|     StanzaFromType addFrom, |  | ||||||
|     bool addId, |  | ||||||
|     bool awaitable, |  | ||||||
|     bool encrypted, |  | ||||||
|     bool forceEncryption, |  | ||||||
|   }) sendStanza; |  | ||||||
| 
 | 
 | ||||||
|   /// Send a nonza. |   /// Send a nonza. | ||||||
|   final void Function(XMLNode) sendNonza; |   final void Function(XMLNode) sendNonza; | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/attributes.dart'; | |||||||
| import 'package:moxxmpp/src/managers/data.dart'; | import 'package:moxxmpp/src/managers/data.dart'; | ||||||
| import 'package:moxxmpp/src/managers/handlers.dart'; | import 'package:moxxmpp/src/managers/handlers.dart'; | ||||||
| import 'package:moxxmpp/src/managers/namespaces.dart'; | import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||||
|  | import 'package:moxxmpp/src/stanza.dart'; | ||||||
| import 'package:moxxmpp/src/stringxml.dart'; | import 'package:moxxmpp/src/stringxml.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; | import 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; | ||||||
| @ -165,9 +166,11 @@ abstract class XmppManagerBase { | |||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     await getAttributes().sendStanza( |     await getAttributes().sendStanza( | ||||||
|       stanza, |       StanzaDetails( | ||||||
|       awaitable: false, |         stanza, | ||||||
|       forceEncryption: data.encrypted, |         awaitable: false, | ||||||
|  |         forceEncryption: data.encrypted, | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -320,6 +320,11 @@ class MessageManager extends XmppManagerBase { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getAttributes().sendStanza(stanza, awaitable: false); |     getAttributes().sendStanza( | ||||||
|  |       StanzaDetails( | ||||||
|  |         stanza, | ||||||
|  |         awaitable: false, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| import 'package:moxxmpp/src/connection.dart'; |  | ||||||
| import 'package:moxxmpp/src/events.dart'; | import 'package:moxxmpp/src/events.dart'; | ||||||
| import 'package:moxxmpp/src/jid.dart'; | import 'package:moxxmpp/src/jid.dart'; | ||||||
| import 'package:moxxmpp/src/managers/base.dart'; | import 'package:moxxmpp/src/managers/base.dart'; | ||||||
| @ -7,10 +6,8 @@ import 'package:moxxmpp/src/managers/data.dart'; | |||||||
| import 'package:moxxmpp/src/managers/handlers.dart'; | import 'package:moxxmpp/src/managers/handlers.dart'; | ||||||
| import 'package:moxxmpp/src/managers/namespaces.dart'; | import 'package:moxxmpp/src/managers/namespaces.dart'; | ||||||
| import 'package:moxxmpp/src/namespaces.dart'; | import 'package:moxxmpp/src/namespaces.dart'; | ||||||
| import 'package:moxxmpp/src/negotiators/namespaces.dart'; |  | ||||||
| import 'package:moxxmpp/src/stanza.dart'; | import 'package:moxxmpp/src/stanza.dart'; | ||||||
| import 'package:moxxmpp/src/stringxml.dart'; | import 'package:moxxmpp/src/stringxml.dart'; | ||||||
| import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart'; |  | ||||||
| 
 | 
 | ||||||
| /// A function that will be called when presence, outside of subscription request | /// 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 | /// management, will be sent. Useful for managers that want to add [XMLNode]s to said | ||||||
| @ -49,12 +46,8 @@ class PresenceManager extends XmppManagerBase { | |||||||
|   Future<void> onXmppEvent(XmppEvent event) async { |   Future<void> onXmppEvent(XmppEvent event) async { | ||||||
|     if (event is StreamNegotiationsDoneEvent) { |     if (event is StreamNegotiationsDoneEvent) { | ||||||
|       // Send initial presence only when we have not resumed the stream |       // Send initial presence only when we have not resumed the stream | ||||||
|       final sm = getAttributes().getNegotiatorById<StreamManagementNegotiator>( |       if (!event.resumed) { | ||||||
|         streamManagementNegotiator, |         await sendInitialPresence(); | ||||||
|       ); |  | ||||||
|       final isResumed = sm?.isResumed ?? false; |  | ||||||
|       if (!isResumed) { |  | ||||||
|         unawaited(sendInitialPresence()); |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -108,66 +101,77 @@ class PresenceManager extends XmppManagerBase { | |||||||
| 
 | 
 | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     await attrs.sendStanza( |     await attrs.sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         from: attrs.getFullJID().toString(), |         Stanza.presence( | ||||||
|         children: children, |           children: children, | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|  |         addId: false, | ||||||
|       ), |       ), | ||||||
|       awaitable: false, |  | ||||||
|       addId: false, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Send an unavailable presence with no 'to' attribute. |   /// Send an unavailable presence with no 'to' attribute. | ||||||
|   void sendUnavailablePresence() { |   void sendUnavailablePresence() { | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         type: 'unavailable', |         Stanza.presence( | ||||||
|  |           type: 'unavailable', | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.full, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Sends a subscription request to [to]. |   /// Sends a subscription request to [to]. | ||||||
|   void sendSubscriptionRequest(String to) { |   void sendSubscriptionRequest(String to) { | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         type: 'subscribe', |         Stanza.presence( | ||||||
|         to: to, |           type: 'subscribe', | ||||||
|  |           to: to, | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.none, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Sends an unsubscription request to [to]. |   /// Sends an unsubscription request to [to]. | ||||||
|   void sendUnsubscriptionRequest(String to) { |   void sendUnsubscriptionRequest(String to) { | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         type: 'unsubscribe', |         Stanza.presence( | ||||||
|         to: to, |           type: 'unsubscribe', | ||||||
|  |           to: to, | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.none, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Accept a presence subscription request for [to]. |   /// Accept a presence subscription request for [to]. | ||||||
|   void sendSubscriptionRequestApproval(String to) { |   void sendSubscriptionRequestApproval(String to) { | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         type: 'subscribed', |         Stanza.presence( | ||||||
|         to: to, |           type: 'subscribed', | ||||||
|  |           to: to, | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.none, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// Reject a presence subscription request for [to]. |   /// Reject a presence subscription request for [to]. | ||||||
|   void sendSubscriptionRequestRejection(String to) { |   void sendSubscriptionRequestRejection(String to) { | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.presence( |       StanzaDetails( | ||||||
|         type: 'unsubscribed', |         Stanza.presence( | ||||||
|         to: to, |           type: 'unsubscribed', | ||||||
|  |           to: to, | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.none, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -235,14 +235,16 @@ class RosterManager extends XmppManagerBase { | |||||||
|       query.attributes['ver'] = rosterVersion; |       query.attributes['ver'] = rosterVersion; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final response = await attrs.sendStanza( |     final response = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         children: [ |           type: 'get', | ||||||
|           query, |           children: [ | ||||||
|         ], |             query, | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (response.attributes['type'] != 'result') { |     if (response.attributes['type'] != 'result') { | ||||||
|       logger.warning('Error requesting roster: ${response.toXml()}'); |       logger.warning('Error requesting roster: ${response.toXml()}'); | ||||||
| @ -258,20 +260,22 @@ class RosterManager extends XmppManagerBase { | |||||||
|   Future<Result<RosterRequestResult?, RosterError>> |   Future<Result<RosterRequestResult?, RosterError>> | ||||||
|       requestRosterPushes() async { |       requestRosterPushes() async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final result = await attrs.sendStanza( |     final result = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         children: [ |           type: 'get', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'query', |             XMLNode.xmlns( | ||||||
|             xmlns: rosterXmlns, |               tag: 'query', | ||||||
|             attributes: { |               xmlns: rosterXmlns, | ||||||
|               'ver': await _stateManager.getRosterVersion() ?? '', |               attributes: { | ||||||
|             }, |                 'ver': await _stateManager.getRosterVersion() ?? '', | ||||||
|           ) |               }, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       logger.warning('Requesting roster pushes failed: ${result.toXml()}'); |       logger.warning('Requesting roster pushes failed: ${result.toXml()}'); | ||||||
| @ -296,31 +300,33 @@ class RosterManager extends XmppManagerBase { | |||||||
|     List<String>? groups, |     List<String>? groups, | ||||||
|   }) async { |   }) async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final response = await attrs.sendStanza( |     final response = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'query', |             XMLNode.xmlns( | ||||||
|             xmlns: rosterXmlns, |               tag: 'query', | ||||||
|             children: [ |               xmlns: rosterXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'item', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'item', | ||||||
|                   'jid': jid, |                   attributes: <String, String>{ | ||||||
|                   ...title == jid.split('@')[0] |                     'jid': jid, | ||||||
|                       ? <String, String>{} |                     ...title == jid.split('@')[0] | ||||||
|                       : <String, String>{'name': title} |                         ? <String, String>{} | ||||||
|                 }, |                         : <String, String>{'name': title} | ||||||
|                 children: (groups ?? []) |                   }, | ||||||
|                     .map((group) => XMLNode(tag: 'group', text: group)) |                   children: (groups ?? []) | ||||||
|                     .toList(), |                       .map((group) => XMLNode(tag: 'group', text: group)) | ||||||
|               ) |                       .toList(), | ||||||
|             ], |                 ) | ||||||
|           ) |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (response.attributes['type'] != 'result') { |     if (response.attributes['type'] != 'result') { | ||||||
|       logger.severe('Error adding $jid to roster: $response'); |       logger.severe('Error adding $jid to roster: $response'); | ||||||
| @ -334,26 +340,28 @@ class RosterManager extends XmppManagerBase { | |||||||
|   /// false otherwise. |   /// false otherwise. | ||||||
|   Future<RosterRemovalResult> removeFromRoster(String jid) async { |   Future<RosterRemovalResult> removeFromRoster(String jid) async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final response = await attrs.sendStanza( |     final response = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'query', |             XMLNode.xmlns( | ||||||
|             xmlns: rosterXmlns, |               tag: 'query', | ||||||
|             children: [ |               xmlns: rosterXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'item', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'item', | ||||||
|                   'jid': jid, |                   attributes: <String, String>{ | ||||||
|                   'subscription': 'remove' |                     'jid': jid, | ||||||
|                 }, |                     'subscription': 'remove' | ||||||
|               ) |                   }, | ||||||
|             ], |                 ) | ||||||
|           ) |               ], | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (response.attributes['type'] != 'result') { |     if (response.attributes['type'] != 'result') { | ||||||
|       logger.severe('Failed to remove roster item: ${response.toXml()}'); |       logger.severe('Failed to remove roster item: ${response.toXml()}'); | ||||||
|  | |||||||
| @ -1,6 +1,30 @@ | |||||||
| import 'package:moxxmpp/src/namespaces.dart'; | import 'package:moxxmpp/src/namespaces.dart'; | ||||||
| import 'package:moxxmpp/src/stringxml.dart'; | import 'package:moxxmpp/src/stringxml.dart'; | ||||||
| 
 | 
 | ||||||
|  | /// A description of a stanza to send. | ||||||
|  | class StanzaDetails { | ||||||
|  |   const StanzaDetails( | ||||||
|  |     this.stanza, { | ||||||
|  |     this.addId = true, | ||||||
|  |     this.awaitable = true, | ||||||
|  |     this.encrypted = false, | ||||||
|  |     this.forceEncryption = false, | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   /// The stanza to send. | ||||||
|  |   final Stanza stanza; | ||||||
|  | 
 | ||||||
|  |   /// Flag indicating whether a stanza id should be added before sending. | ||||||
|  |   final bool addId; | ||||||
|  | 
 | ||||||
|  |   /// Track the stanza to allow awaiting its response. | ||||||
|  |   final bool awaitable; | ||||||
|  | 
 | ||||||
|  |   final bool encrypted; | ||||||
|  | 
 | ||||||
|  |   final bool forceEncryption; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// A simple description of the <error /> element that may be inside a stanza | /// A simple description of the <error /> element that may be inside a stanza | ||||||
| class StanzaError { | class StanzaError { | ||||||
|   StanzaError(this.type, this.error); |   StanzaError(this.type, this.error); | ||||||
|  | |||||||
| @ -1,37 +1,68 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| import 'dart:collection'; | import 'dart:collection'; | ||||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||||
|  | import 'package:moxxmpp/src/stanza.dart'; | ||||||
|  | import 'package:moxxmpp/src/stringxml.dart'; | ||||||
| import 'package:synchronized/synchronized.dart'; | import 'package:synchronized/synchronized.dart'; | ||||||
| 
 | 
 | ||||||
| /// A job to be submitted to an [AsyncQueue]. | class StanzaQueueEntry { | ||||||
| typedef AsyncQueueJob = Future<void> Function(); |   const StanzaQueueEntry( | ||||||
|  |     this.details, | ||||||
|  |     this.completer, | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   /// The actual data to send. | ||||||
|  |   final StanzaDetails details; | ||||||
|  | 
 | ||||||
|  |   /// The [Completer] to resolve when the response is received. | ||||||
|  |   final Completer<XMLNode>? completer; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A function that is executed when a job is popped from the queue. | ||||||
|  | typedef SendStanzaFunction = Future<void> Function(StanzaQueueEntry); | ||||||
|  | 
 | ||||||
|  | /// A function that is called before popping a queue item. Should return true when | ||||||
|  | /// the [SendStanzaFunction] can be executed. | ||||||
|  | typedef CanSendCallback = Future<bool> Function(); | ||||||
| 
 | 
 | ||||||
| /// A (hopefully) async-safe queue that attempts to force | /// A (hopefully) async-safe queue that attempts to force | ||||||
| /// in-order execution of its jobs. | /// in-order execution of its jobs. | ||||||
| class AsyncQueue { | class AsyncStanzaQueue { | ||||||
|   /// The lock for accessing [AsyncQueue._lock] and [AsyncQueue._running]. |   AsyncStanzaQueue( | ||||||
|  |     this._sendStanzaFunction, | ||||||
|  |     this._canSendCallback, | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   /// The lock for accessing [AsyncStanzaQueue._lock] and [AsyncStanzaQueue._running]. | ||||||
|   final Lock _lock = Lock(); |   final Lock _lock = Lock(); | ||||||
| 
 | 
 | ||||||
|   /// The actual job queue. |   /// The actual job queue. | ||||||
|   final Queue<AsyncQueueJob> _queue = Queue<AsyncQueueJob>(); |   final Queue<StanzaQueueEntry> _queue = Queue<StanzaQueueEntry>(); | ||||||
|  | 
 | ||||||
|  |   /// Sends the stanza when we can pop from the queue. | ||||||
|  |   final SendStanzaFunction _sendStanzaFunction; | ||||||
|  | 
 | ||||||
|  |   final CanSendCallback _canSendCallback; | ||||||
| 
 | 
 | ||||||
|   /// Indicates whether we are currently executing a job. |   /// Indicates whether we are currently executing a job. | ||||||
|   bool _running = false; |   bool _running = false; | ||||||
| 
 | 
 | ||||||
|   @visibleForTesting |   @visibleForTesting | ||||||
|   Queue<AsyncQueueJob> get queue => _queue; |   Queue<StanzaQueueEntry> get queue => _queue; | ||||||
| 
 | 
 | ||||||
|   @visibleForTesting |   @visibleForTesting | ||||||
|   bool get isRunning => _running; |   bool get isRunning => _running; | ||||||
| 
 | 
 | ||||||
|   /// Adds a job [job] to the queue. |   /// Adds a job [entry] to the queue. | ||||||
|   Future<void> addJob(AsyncQueueJob job) async { |   Future<void> enqueueStanza(StanzaQueueEntry entry) async { | ||||||
|     await _lock.synchronized(() { |     await _lock.synchronized(() async { | ||||||
|       _queue.add(job); |       _queue.add(entry); | ||||||
| 
 | 
 | ||||||
|       if (!_running && _queue.isNotEmpty) { |       if (!_running && _queue.isNotEmpty && await _canSendCallback()) { | ||||||
|         _running = true; |         _running = true; | ||||||
|         unawaited(_popJob()); |         unawaited( | ||||||
|  |           _runJob(_queue.removeFirst()), | ||||||
|  |         ); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @ -40,17 +71,30 @@ class AsyncQueue { | |||||||
|     await _lock.synchronized(_queue.clear); |     await _lock.synchronized(_queue.clear); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<void> _popJob() async { |   Future<void> _runJob(StanzaQueueEntry details) async { | ||||||
|     final job = _queue.removeFirst(); |     await _sendStanzaFunction(details); | ||||||
|     final future = job(); |  | ||||||
|     await future; |  | ||||||
| 
 | 
 | ||||||
|     await _lock.synchronized(() { |     await _lock.synchronized(() async { | ||||||
|       if (_queue.isNotEmpty) { |       if (_queue.isNotEmpty && await _canSendCallback()) { | ||||||
|         unawaited(_popJob()); |         unawaited( | ||||||
|  |           _runJob(_queue.removeFirst()), | ||||||
|  |         ); | ||||||
|       } else { |       } else { | ||||||
|         _running = false; |         _running = false; | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   Future<void> restart() async { | ||||||
|  |     if (!(await _canSendCallback())) return; | ||||||
|  | 
 | ||||||
|  |     await _lock.synchronized(() { | ||||||
|  |       if (_queue.isNotEmpty) { | ||||||
|  |         _running = true; | ||||||
|  |         unawaited( | ||||||
|  |           _runJob(_queue.removeFirst()), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -291,10 +291,12 @@ class DiscoManager extends XmppManagerBase { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final stanza = await getAttributes().sendStanza( |     final stanza = (await getAttributes().sendStanza( | ||||||
|       buildDiscoInfoQueryStanza(entity, node), |       StanzaDetails( | ||||||
|       encrypted: !shouldEncrypt, |         buildDiscoInfoQueryStanza(entity, node), | ||||||
|     ); |         encrypted: !shouldEncrypt, | ||||||
|  |       ), | ||||||
|  |     ))!; | ||||||
|     final query = stanza.firstTag('query'); |     final query = stanza.firstTag('query'); | ||||||
|     if (query == null) { |     if (query == null) { | ||||||
|       final result = Result<DiscoError, DiscoInfo>(InvalidResponseDiscoError()); |       final result = Result<DiscoError, DiscoInfo>(InvalidResponseDiscoError()); | ||||||
| @ -331,10 +333,12 @@ class DiscoManager extends XmppManagerBase { | |||||||
|       return future; |       return future; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final stanza = await getAttributes().sendStanza( |     final stanza = (await getAttributes().sendStanza( | ||||||
|       buildDiscoItemsQueryStanza(entity, node: node), |       StanzaDetails( | ||||||
|       encrypted: !shouldEncrypt, |         buildDiscoItemsQueryStanza(entity, node: node), | ||||||
|     ) as Stanza; |         encrypted: !shouldEncrypt, | ||||||
|  |       ), | ||||||
|  |     ))!; | ||||||
| 
 | 
 | ||||||
|     final query = stanza.firstTag('query'); |     final query = stanza.firstTag('query'); | ||||||
|     if (query == null) { |     if (query == null) { | ||||||
| @ -344,7 +348,7 @@ class DiscoManager extends XmppManagerBase { | |||||||
|       return result; |       return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (stanza.type == 'error') { |     if (stanza.attributes['type'] == 'error') { | ||||||
|       //final error = stanza.firstTag('error'); |       //final error = stanza.firstTag('error'); | ||||||
|       //print("Disco Items error: " + error.toXml()); |       //print("Disco Items error: " + error.toXml()); | ||||||
|       final result = |       final result = | ||||||
|  | |||||||
| @ -103,19 +103,21 @@ class VCardManager extends XmppManagerBase { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<Result<VCardError, VCard>> requestVCard(String jid) async { |   Future<Result<VCardError, VCard>> requestVCard(String jid) async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         to: jid, |         Stanza.iq( | ||||||
|         type: 'get', |           to: jid, | ||||||
|         children: [ |           type: 'get', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'vCard', |             XMLNode.xmlns( | ||||||
|             xmlns: vCardTempXmlns, |               tag: 'vCard', | ||||||
|           ) |               xmlns: vCardTempXmlns, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |         encrypted: true, | ||||||
|       ), |       ), | ||||||
|       encrypted: true, |     ))!; | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       return Result(UnknownVCardError()); |       return Result(UnknownVCardError()); | ||||||
|  | |||||||
| @ -181,27 +181,29 @@ class PubSubManager extends XmppManagerBase { | |||||||
| 
 | 
 | ||||||
|   Future<Result<PubSubError, bool>> subscribe(String jid, String node) async { |   Future<Result<PubSubError, bool>> subscribe(String jid, String node) async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final result = await attrs.sendStanza( |     final result = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: jid, |           type: 'set', | ||||||
|         children: [ |           to: jid, | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'subscribe', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'subscribe', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                   'jid': attrs.getFullJID().toBare().toString(), |                     'node': node, | ||||||
|                 }, |                     'jid': attrs.getFullJID().toBare().toString(), | ||||||
|               ), |                   }, | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       return Result(UnknownPubSubError()); |       return Result(UnknownPubSubError()); | ||||||
| @ -222,27 +224,29 @@ class PubSubManager extends XmppManagerBase { | |||||||
| 
 | 
 | ||||||
|   Future<Result<PubSubError, bool>> unsubscribe(String jid, String node) async { |   Future<Result<PubSubError, bool>> unsubscribe(String jid, String node) async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final result = await attrs.sendStanza( |     final result = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: jid, |           type: 'set', | ||||||
|         children: [ |           to: jid, | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'unsubscribe', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'unsubscribe', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                   'jid': attrs.getFullJID().toBare().toString(), |                     'node': node, | ||||||
|                 }, |                     'jid': attrs.getFullJID().toBare().toString(), | ||||||
|               ), |                   }, | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       return Result(UnknownPubSubError()); |       return Result(UnknownPubSubError()); | ||||||
| @ -293,38 +297,40 @@ class PubSubManager extends XmppManagerBase { | |||||||
|       pubOptions = await preprocessPublishOptions(jid, node, options); |       pubOptions = await preprocessPublishOptions(jid, node, options); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: jid.toString(), |           type: 'set', | ||||||
|         children: [ |           to: jid.toString(), | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'publish', |  | ||||||
|                 attributes: <String, String>{'node': node}, |  | ||||||
|                 children: [ |  | ||||||
|                   XMLNode( |  | ||||||
|                     tag: 'item', |  | ||||||
|                     attributes: id != null |  | ||||||
|                         ? <String, String>{'id': id} |  | ||||||
|                         : <String, String>{}, |  | ||||||
|                     children: [payload], |  | ||||||
|                   ) |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|               if (pubOptions != null) |  | ||||||
|                 XMLNode( |                 XMLNode( | ||||||
|                   tag: 'publish-options', |                   tag: 'publish', | ||||||
|                   children: [pubOptions.toXml()], |                   attributes: <String, String>{'node': node}, | ||||||
|  |                   children: [ | ||||||
|  |                     XMLNode( | ||||||
|  |                       tag: 'item', | ||||||
|  |                       attributes: id != null | ||||||
|  |                           ? <String, String>{'id': id} | ||||||
|  |                           : <String, String>{}, | ||||||
|  |                       children: [payload], | ||||||
|  |                     ) | ||||||
|  |                   ], | ||||||
|                 ), |                 ), | ||||||
|             ], |                 if (pubOptions != null) | ||||||
|           ) |                   XMLNode( | ||||||
|         ], |                     tag: 'publish-options', | ||||||
|  |                     children: [pubOptions.toXml()], | ||||||
|  |                   ), | ||||||
|  |               ], | ||||||
|  |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       final error = getPubSubError(result); |       final error = getPubSubError(result); | ||||||
| 
 | 
 | ||||||
| @ -395,21 +401,26 @@ class PubSubManager extends XmppManagerBase { | |||||||
|     String jid, |     String jid, | ||||||
|     String node, |     String node, | ||||||
|   ) async { |   ) async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         to: jid, |           type: 'get', | ||||||
|         children: [ |           to: jid, | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode(tag: 'items', attributes: <String, String>{'node': node}), |               children: [ | ||||||
|             ], |                 XMLNode( | ||||||
|           ) |                   tag: 'items', | ||||||
|         ], |                   attributes: <String, String>{'node': node}, | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       return Result(getPubSubError(result)); |       return Result(getPubSubError(result)); | ||||||
| @ -436,30 +447,32 @@ class PubSubManager extends XmppManagerBase { | |||||||
|     String node, |     String node, | ||||||
|     String id, |     String id, | ||||||
|   ) async { |   ) async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         to: jid, |           type: 'get', | ||||||
|         children: [ |           to: jid, | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'items', |                 XMLNode( | ||||||
|                 attributes: <String, String>{'node': node}, |                   tag: 'items', | ||||||
|                 children: [ |                   attributes: <String, String>{'node': node}, | ||||||
|                   XMLNode( |                   children: [ | ||||||
|                     tag: 'item', |                     XMLNode( | ||||||
|                     attributes: <String, String>{'id': id}, |                       tag: 'item', | ||||||
|                   ), |                       attributes: <String, String>{'id': id}, | ||||||
|                 ], |                     ), | ||||||
|               ), |                   ], | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       return Result(getPubSubError(result)); |       return Result(getPubSubError(result)); | ||||||
| @ -488,53 +501,57 @@ class PubSubManager extends XmppManagerBase { | |||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
| 
 | 
 | ||||||
|     // Request the form |     // Request the form | ||||||
|     final form = await attrs.sendStanza( |     final form = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         to: jid.toString(), |           type: 'get', | ||||||
|         children: [ |           to: jid.toString(), | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubOwnerXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubOwnerXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'configure', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'configure', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                 }, |                     'node': node, | ||||||
|               ), |                   }, | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
|     if (form.attributes['type'] != 'result') { |     if (form.attributes['type'] != 'result') { | ||||||
|       return Result(getPubSubError(form)); |       return Result(getPubSubError(form)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final submit = await attrs.sendStanza( |     final submit = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: jid.toString(), |           type: 'set', | ||||||
|         children: [ |           to: jid.toString(), | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubOwnerXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubOwnerXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'configure', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'configure', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                 }, |                     'node': node, | ||||||
|                 children: [ |                   }, | ||||||
|                   options.toXml(), |                   children: [ | ||||||
|                 ], |                     options.toXml(), | ||||||
|               ), |                   ], | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
|     if (submit.attributes['type'] != 'result') { |     if (submit.attributes['type'] != 'result') { | ||||||
|       return Result(getPubSubError(form)); |       return Result(getPubSubError(form)); | ||||||
|     } |     } | ||||||
| @ -543,28 +560,30 @@ class PubSubManager extends XmppManagerBase { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<Result<PubSubError, bool>> delete(JID host, String node) async { |   Future<Result<PubSubError, bool>> delete(JID host, String node) async { | ||||||
|     final request = await getAttributes().sendStanza( |     final request = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: host.toString(), |           type: 'set', | ||||||
|         children: [ |           to: host.toString(), | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubOwnerXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubOwnerXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'delete', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'delete', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                 }, |                     'node': node, | ||||||
|               ), |                   }, | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ) as Stanza; |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (request.type != 'result') { |     if (request.attributes['type'] != 'result') { | ||||||
|       // TODO(Unknown): Be more specific |       // TODO(Unknown): Be more specific | ||||||
|       return Result(UnknownPubSubError()); |       return Result(UnknownPubSubError()); | ||||||
|     } |     } | ||||||
| @ -577,36 +596,38 @@ class PubSubManager extends XmppManagerBase { | |||||||
|     String node, |     String node, | ||||||
|     String itemId, |     String itemId, | ||||||
|   ) async { |   ) async { | ||||||
|     final request = await getAttributes().sendStanza( |     final request = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         to: host.toString(), |           type: 'set', | ||||||
|         children: [ |           to: host.toString(), | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'pubsub', |             XMLNode.xmlns( | ||||||
|             xmlns: pubsubXmlns, |               tag: 'pubsub', | ||||||
|             children: [ |               xmlns: pubsubXmlns, | ||||||
|               XMLNode( |               children: [ | ||||||
|                 tag: 'retract', |                 XMLNode( | ||||||
|                 attributes: <String, String>{ |                   tag: 'retract', | ||||||
|                   'node': node, |                   attributes: <String, String>{ | ||||||
|                 }, |                     'node': node, | ||||||
|                 children: [ |                   }, | ||||||
|                   XMLNode( |                   children: [ | ||||||
|                     tag: 'item', |                     XMLNode( | ||||||
|                     attributes: <String, String>{ |                       tag: 'item', | ||||||
|                       'id': itemId, |                       attributes: <String, String>{ | ||||||
|                     }, |                         'id': itemId, | ||||||
|                   ), |                       }, | ||||||
|                 ], |                     ), | ||||||
|               ), |                   ], | ||||||
|             ], |                 ), | ||||||
|           ), |               ], | ||||||
|         ], |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ) as Stanza; |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (request.type != 'result') { |     if (request.attributes['type'] != 'result') { | ||||||
|       // TODO(Unknown): Be more specific |       // TODO(Unknown): Be more specific | ||||||
|       return Result(UnknownPubSubError()); |       return Result(UnknownPubSubError()); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -111,10 +111,14 @@ class ChatStateManager extends XmppManagerBase { | |||||||
|     final tagName = state.toString().split('.').last; |     final tagName = state.toString().split('.').last; | ||||||
| 
 | 
 | ||||||
|     getAttributes().sendStanza( |     getAttributes().sendStanza( | ||||||
|       Stanza.message( |       StanzaDetails( | ||||||
|         to: to, |         Stanza.message( | ||||||
|         type: messageType, |           to: to, | ||||||
|         children: [XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns)], |           type: messageType, | ||||||
|  |           children: [ | ||||||
|  |             XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -96,39 +96,43 @@ class BlockingManager extends XmppManagerBase { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<bool> block(List<String> items) async { |   Future<bool> block(List<String> items) async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'block', |             XMLNode.xmlns( | ||||||
|             xmlns: blockingXmlns, |               tag: 'block', | ||||||
|             children: items.map((item) { |               xmlns: blockingXmlns, | ||||||
|               return XMLNode( |               children: items.map((item) { | ||||||
|                 tag: 'item', |                 return XMLNode( | ||||||
|                 attributes: <String, String>{'jid': item}, |                   tag: 'item', | ||||||
|               ); |                   attributes: <String, String>{'jid': item}, | ||||||
|             }).toList(), |                 ); | ||||||
|           ) |               }).toList(), | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     return result.attributes['type'] == 'result'; |     return result.attributes['type'] == 'result'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<bool> unblockAll() async { |   Future<bool> unblockAll() async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'unblock', |             XMLNode.xmlns( | ||||||
|             xmlns: blockingXmlns, |               tag: 'unblock', | ||||||
|           ) |               xmlns: blockingXmlns, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     return result.attributes['type'] == 'result'; |     return result.attributes['type'] == 'result'; | ||||||
|   } |   } | ||||||
| @ -136,41 +140,45 @@ class BlockingManager extends XmppManagerBase { | |||||||
|   Future<bool> unblock(List<String> items) async { |   Future<bool> unblock(List<String> items) async { | ||||||
|     assert(items.isNotEmpty, 'The list of items to unblock must be non-empty'); |     assert(items.isNotEmpty, 'The list of items to unblock must be non-empty'); | ||||||
| 
 | 
 | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'unblock', |             XMLNode.xmlns( | ||||||
|             xmlns: blockingXmlns, |               tag: 'unblock', | ||||||
|             children: items |               xmlns: blockingXmlns, | ||||||
|                 .map( |               children: items | ||||||
|                   (item) => XMLNode( |                   .map( | ||||||
|                     tag: 'item', |                     (item) => XMLNode( | ||||||
|                     attributes: <String, String>{'jid': item}, |                       tag: 'item', | ||||||
|                   ), |                       attributes: <String, String>{'jid': item}, | ||||||
|                 ) |                     ), | ||||||
|                 .toList(), |                   ) | ||||||
|           ) |                   .toList(), | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     return result.attributes['type'] == 'result'; |     return result.attributes['type'] == 'result'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Future<List<String>> getBlocklist() async { |   Future<List<String>> getBlocklist() async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'get', |         Stanza.iq( | ||||||
|         children: [ |           type: 'get', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'blocklist', |             XMLNode.xmlns( | ||||||
|             xmlns: blockingXmlns, |               tag: 'blocklist', | ||||||
|           ) |               xmlns: blockingXmlns, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     final blocklist = result.firstTag('blocklist', xmlns: blockingXmlns)!; |     final blocklist = result.firstTag('blocklist', xmlns: blockingXmlns)!; | ||||||
|     return blocklist |     return blocklist | ||||||
|  | |||||||
| @ -414,7 +414,12 @@ class StreamManagementManager extends XmppManagerBase { | |||||||
|     _unackedStanzas.clear(); |     _unackedStanzas.clear(); | ||||||
| 
 | 
 | ||||||
|     for (final stanza in stanzas) { |     for (final stanza in stanzas) { | ||||||
|       await getAttributes().sendStanza(stanza, awaitable: false); |       await getAttributes().sendStanza( | ||||||
|  |         StanzaDetails( | ||||||
|  |           stanza, | ||||||
|  |           awaitable: false, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import 'package:logging/logging.dart'; | import 'package:logging/logging.dart'; | ||||||
| import 'package:meta/meta.dart'; | import 'package:meta/meta.dart'; | ||||||
| import 'package:moxxmpp/src/connection.dart'; |  | ||||||
| import 'package:moxxmpp/src/events.dart'; | import 'package:moxxmpp/src/events.dart'; | ||||||
| import 'package:moxxmpp/src/jid.dart'; | import 'package:moxxmpp/src/jid.dart'; | ||||||
| import 'package:moxxmpp/src/managers/base.dart'; | import 'package:moxxmpp/src/managers/base.dart'; | ||||||
| @ -111,20 +110,20 @@ class CarbonsManager extends XmppManagerBase { | |||||||
|   /// Returns true if carbons were enabled. False, if not. |   /// Returns true if carbons were enabled. False, if not. | ||||||
|   Future<bool> enableCarbons() async { |   Future<bool> enableCarbons() async { | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final result = await attrs.sendStanza( |     final result = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         to: attrs.getFullJID().toBare().toString(), |         Stanza.iq( | ||||||
|         type: 'set', |           to: attrs.getFullJID().toBare().toString(), | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'enable', |             XMLNode.xmlns( | ||||||
|             xmlns: carbonsXmlns, |               tag: 'enable', | ||||||
|           ) |               xmlns: carbonsXmlns, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.full, |     ))!; | ||||||
|       addId: true, |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       logger.warning('Failed to enable message carbons'); |       logger.warning('Failed to enable message carbons'); | ||||||
| @ -142,19 +141,19 @@ class CarbonsManager extends XmppManagerBase { | |||||||
|   /// |   /// | ||||||
|   /// Returns true if carbons were disabled. False, if not. |   /// Returns true if carbons were disabled. False, if not. | ||||||
|   Future<bool> disableCarbons() async { |   Future<bool> disableCarbons() async { | ||||||
|     final result = await getAttributes().sendStanza( |     final result = (await getAttributes().sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         type: 'set', |         Stanza.iq( | ||||||
|         children: [ |           type: 'set', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'disable', |             XMLNode.xmlns( | ||||||
|             xmlns: carbonsXmlns, |               tag: 'disable', | ||||||
|           ) |               xmlns: carbonsXmlns, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|       addFrom: StanzaFromType.full, |     ))!; | ||||||
|       addId: true, |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     if (result.attributes['type'] != 'result') { |     if (result.attributes['type'] != 'result') { | ||||||
|       logger.warning('Failed to disable message carbons'); |       logger.warning('Failed to disable message carbons'); | ||||||
|  | |||||||
| @ -149,23 +149,25 @@ class HttpFileUploadManager extends XmppManagerBase { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     final attrs = getAttributes(); |     final attrs = getAttributes(); | ||||||
|     final response = await attrs.sendStanza( |     final response = (await attrs.sendStanza( | ||||||
|       Stanza.iq( |       StanzaDetails( | ||||||
|         to: _entityJid.toString(), |         Stanza.iq( | ||||||
|         type: 'get', |           to: _entityJid.toString(), | ||||||
|         children: [ |           type: 'get', | ||||||
|           XMLNode.xmlns( |           children: [ | ||||||
|             tag: 'request', |             XMLNode.xmlns( | ||||||
|             xmlns: httpFileUploadXmlns, |               tag: 'request', | ||||||
|             attributes: { |               xmlns: httpFileUploadXmlns, | ||||||
|               'filename': filename, |               attributes: { | ||||||
|               'size': filesize.toString(), |                 'filename': filename, | ||||||
|               ...contentType != null ? {'content-type': contentType} : {} |                 'size': filesize.toString(), | ||||||
|             }, |                 ...contentType != null ? {'content-type': contentType} : {} | ||||||
|           ) |               }, | ||||||
|         ], |             ) | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ))!; | ||||||
| 
 | 
 | ||||||
|     if (response.attributes['type']! != 'result') { |     if (response.attributes['type']! != 'result') { | ||||||
|       logger.severe('Failed to request HTTP File Upload slot.'); |       logger.severe('Failed to request HTTP File Upload slot.'); | ||||||
|  | |||||||
| @ -262,24 +262,26 @@ abstract class BaseOmemoManager extends XmppManagerBase { | |||||||
|     String toJid, |     String toJid, | ||||||
|   ) async { |   ) async { | ||||||
|     await getAttributes().sendStanza( |     await getAttributes().sendStanza( | ||||||
|       Stanza.message( |       StanzaDetails( | ||||||
|         to: toJid, |         Stanza.message( | ||||||
|         type: 'chat', |           to: toJid, | ||||||
|         children: [ |           type: 'chat', | ||||||
|           _buildEncryptedElement( |           children: [ | ||||||
|             result, |             _buildEncryptedElement( | ||||||
|             toJid, |               result, | ||||||
|             await _getDeviceId(), |               toJid, | ||||||
|           ), |               await _getDeviceId(), | ||||||
|  |             ), | ||||||
| 
 | 
 | ||||||
|           // Add a storage hint in case this is a message |             // Add a storage hint in case this is a message | ||||||
|           // Taken from the example at |             // Taken from the example at | ||||||
|           // https://xmpp.org/extensions/xep-0384.html#message-structure-description. |             // https://xmpp.org/extensions/xep-0384.html#message-structure-description. | ||||||
|           MessageProcessingHint.store.toXml(), |             MessageProcessingHint.store.toXml(), | ||||||
|         ], |           ], | ||||||
|  |         ), | ||||||
|  |         awaitable: false, | ||||||
|  |         encrypted: true, | ||||||
|       ), |       ), | ||||||
|       awaitable: false, |  | ||||||
|       encrypted: true, |  | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,58 +1,100 @@ | |||||||
|  | import 'package:moxxmpp/moxxmpp.dart'; | ||||||
| import 'package:moxxmpp/src/util/queue.dart'; | import 'package:moxxmpp/src/util/queue.dart'; | ||||||
| import 'package:test/test.dart'; | import 'package:test/test.dart'; | ||||||
| 
 | 
 | ||||||
| void main() { | void main() { | ||||||
|   test('Test the async queue', () async { |   test('Test not sending', () async { | ||||||
|     final queue = AsyncQueue(); |     final queue = AsyncStanzaQueue( | ||||||
|     var future1Finish = 0; |       (entry) async { | ||||||
|     var future2Finish = 0; |         assert(false, 'No stanza should be sent'); | ||||||
|     var future3Finish = 0; |       }, | ||||||
|  |       () async => false, | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     await queue.addJob( |     await queue.enqueueStanza( | ||||||
|       () => Future<void>.delayed( |       StanzaQueueEntry( | ||||||
|         const Duration(seconds: 3), |         StanzaDetails( | ||||||
|         () => future1Finish = DateTime.now().millisecondsSinceEpoch, |           Stanza.message(), | ||||||
|  |         ), | ||||||
|  |         null, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|     await queue.addJob( |     await queue.enqueueStanza( | ||||||
|       () => Future<void>.delayed( |       StanzaQueueEntry( | ||||||
|         const Duration(seconds: 3), |         StanzaDetails( | ||||||
|         () => future2Finish = DateTime.now().millisecondsSinceEpoch, |           Stanza.message(), | ||||||
|       ), |         ), | ||||||
|     ); |         null, | ||||||
|     await queue.addJob( |  | ||||||
|       () => Future<void>.delayed( |  | ||||||
|         const Duration(seconds: 3), |  | ||||||
|         () => future3Finish = DateTime.now().millisecondsSinceEpoch, |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     await Future<void>.delayed(const Duration(seconds: 12)); |     await Future<void>.delayed(const Duration(seconds: 1)); | ||||||
|  |     expect(queue.queue.length, 2); | ||||||
|  |     expect(queue.isRunning, false); | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
|     // The three futures must be done |   test('Test sending', () async { | ||||||
|     expect(future1Finish != 0, true); |     final queue = AsyncStanzaQueue( | ||||||
|     expect(future2Finish != 0, true); |       (entry) async {}, | ||||||
|     expect(future3Finish != 0, true); |       () async => 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 |     await queue.enqueueStanza( | ||||||
|     expect(queue.queue.isEmpty, true); |       StanzaQueueEntry( | ||||||
|  |         StanzaDetails( | ||||||
|  |           Stanza.message(), | ||||||
|  |         ), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |     await queue.enqueueStanza( | ||||||
|  |       StanzaQueueEntry( | ||||||
|  |         StanzaDetails( | ||||||
|  |           Stanza.message(), | ||||||
|  |         ), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     // The queue must not be executing anything at the end |     await Future<void>.delayed(const Duration(seconds: 1)); | ||||||
|  |     expect(queue.queue.length, 0); | ||||||
|  |     expect(queue.isRunning, false); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   test('Test partial sending and resuming', () async { | ||||||
|  |     var canRun = true; | ||||||
|  |     final queue = AsyncStanzaQueue( | ||||||
|  |       (entry) async { | ||||||
|  |         canRun = false; | ||||||
|  |       }, | ||||||
|  |       () async => canRun, | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     await queue.enqueueStanza( | ||||||
|  |       StanzaQueueEntry( | ||||||
|  |         StanzaDetails( | ||||||
|  |           Stanza.message(), | ||||||
|  |         ), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |     await queue.enqueueStanza( | ||||||
|  |       StanzaQueueEntry( | ||||||
|  |         StanzaDetails( | ||||||
|  |           Stanza.message(), | ||||||
|  |         ), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     await Future<void>.delayed(const Duration(seconds: 1)); | ||||||
|  |     expect(queue.queue.length, 1); | ||||||
|  |     expect(queue.isRunning, false); | ||||||
|  | 
 | ||||||
|  |     canRun = true; | ||||||
|  |     await queue.restart(); | ||||||
|  |     await Future<void>.delayed(const Duration(seconds: 1)); | ||||||
|  |     expect(queue.queue.length, 0); | ||||||
|     expect(queue.isRunning, false); |     expect(queue.isRunning, false); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -33,7 +33,6 @@ class TestingManagerHolder { | |||||||
| 
 | 
 | ||||||
|   Future<XMLNode> _sendStanza( |   Future<XMLNode> _sendStanza( | ||||||
|     stanza, { |     stanza, { | ||||||
|     StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|     bool addId = true, |     bool addId = true, | ||||||
|     bool awaitable = true, |     bool awaitable = true, | ||||||
|     bool encrypted = false, |     bool encrypted = false, | ||||||
|  | |||||||
| @ -98,7 +98,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) { | |||||||
|       ignoreId: true, |       ignoreId: true, | ||||||
|     ), |     ), | ||||||
|     StanzaExpectation( |     StanzaExpectation( | ||||||
|       "<presence xmlns='jabber:client' from='${settings.jid.toBare()}/MU29eEZn'><show>chat</show></presence>", |       "<presence xmlns='jabber:client'><show>chat</show></presence>", | ||||||
|       '', |       '', | ||||||
|     ), |     ), | ||||||
|   ]; |   ]; | ||||||
|  | |||||||
| @ -58,11 +58,11 @@ void main() { | |||||||
|           ignoreId: true, |           ignoreId: true, | ||||||
|         ), |         ), | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           "<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'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>", | ||||||
|           '', |           '', | ||||||
|         ), |         ), | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           "<iq type='get' id='ec325efc-9924-4c48-93f8-ed34a2b0e5fc' to='romeo@montague.lit/orchard' from='polynomdivision@test.server/MU29eEZn' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>", |           "<iq type='get' id='ec325efc-9924-4c48-93f8-ed34a2b0e5fc' to='romeo@montague.lit/orchard' xmlns='jabber:client'><query xmlns='http://jabber.org/protocol/disco#info' /></iq>", | ||||||
|           '', |           '', | ||||||
|           ignoreId: true, |           ignoreId: true, | ||||||
|         ), |         ), | ||||||
|  | |||||||
| @ -92,7 +92,7 @@ void main() { | |||||||
|       [ |       [ | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           ''' |           ''' | ||||||
| <iq type="get" to="pubsub.server.example.org" id="a" from="testuser@example.org/MU29eEZn" xmlns="jabber:client"> | <iq type="get" to="pubsub.server.example.org" id="a" xmlns="jabber:client"> | ||||||
|   <query xmlns="http://jabber.org/protocol/disco#info" /> |   <query xmlns="http://jabber.org/protocol/disco#info" /> | ||||||
| </iq> | </iq> | ||||||
| ''', | ''', | ||||||
| @ -110,7 +110,7 @@ void main() { | |||||||
|         ), |         ), | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           ''' |           ''' | ||||||
| <iq type="get" to="pubsub.server.example.org" id="a" from="testuser@example.org/MU29eEZn" xmlns="jabber:client"> | <iq type="get" to="pubsub.server.example.org" id="a" xmlns="jabber:client"> | ||||||
|   <query xmlns="http://jabber.org/protocol/disco#items" node="princely_musings" /> |   <query xmlns="http://jabber.org/protocol/disco#items" node="princely_musings" /> | ||||||
| </iq> | </iq> | ||||||
| ''', | ''', | ||||||
| @ -124,7 +124,7 @@ void main() { | |||||||
|         ), |         ), | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           ''' |           ''' | ||||||
| <iq type="set" to="pubsub.server.example.org" id="a" from="testuser@example.org/MU29eEZn" xmlns="jabber:client"> | <iq type="set" to="pubsub.server.example.org" id="a" xmlns="jabber:client"> | ||||||
|   <pubsub xmlns='http://jabber.org/protocol/pubsub'> |   <pubsub xmlns='http://jabber.org/protocol/pubsub'> | ||||||
|     <publish node='princely_musings'> |     <publish node='princely_musings'> | ||||||
|       <item id="current"> |       <item id="current"> | ||||||
|  | |||||||
| @ -44,15 +44,8 @@ Future<void> runOutgoingStanzaHandlers( | |||||||
| 
 | 
 | ||||||
| XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { | XmppManagerAttributes mkAttributes(void Function(Stanza) callback) { | ||||||
|   return XmppManagerAttributes( |   return XmppManagerAttributes( | ||||||
|     sendStanza: ( |     sendStanza: (StanzaDetails details) async { | ||||||
|       stanza, { |       callback(details.stanza); | ||||||
|       StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|       bool addId = true, |  | ||||||
|       bool awaitable = true, |  | ||||||
|       bool encrypted = false, |  | ||||||
|       bool forceEncryption = false, |  | ||||||
|     }) async { |  | ||||||
|       callback(stanza); |  | ||||||
| 
 | 
 | ||||||
|       return Stanza.message(); |       return Stanza.message(); | ||||||
|     }, |     }, | ||||||
| @ -290,12 +283,8 @@ void main() { | |||||||
|         ); |         ); | ||||||
|       final sm = StreamManagementManager(); |       final sm = StreamManagementManager(); | ||||||
|       await conn.registerManagers([ |       await conn.registerManagers([ | ||||||
|         PresenceManager(), |  | ||||||
|         RosterManager(TestingRosterStateManager('', [])), |  | ||||||
|         DiscoManager([]), |  | ||||||
|         sm, |         sm, | ||||||
|         CarbonsManager()..forceEnable(), |         CarbonsManager()..forceEnable(), | ||||||
|         EntityCapabilitiesManager('http://moxxmpp.example'), |  | ||||||
|       ]); |       ]); | ||||||
|       await conn.registerFeatureNegotiators([ |       await conn.registerFeatureNegotiators([ | ||||||
|         SaslPlainNegotiator(), |         SaslPlainNegotiator(), | ||||||
| @ -391,7 +380,7 @@ void main() { | |||||||
|           '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />', |           '<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />', | ||||||
|         ), |         ), | ||||||
|         StanzaExpectation( |         StanzaExpectation( | ||||||
|           "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>", |           "<presence xmlns='jabber:client'><show>chat</show></presence>", | ||||||
|           '<iq type="result" />', |           '<iq type="result" />', | ||||||
|         ), |         ), | ||||||
|         StringExpectation( |         StringExpectation( | ||||||
| @ -681,7 +670,7 @@ void main() { | |||||||
|         "<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />", |         "<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />", | ||||||
|       ), |       ), | ||||||
|       StanzaExpectation( |       StanzaExpectation( | ||||||
|         "<iq to='localhost' type='get' from='polynomdivision@test.server/abc123' xmlns='jabber:client' />", |         "<iq to='localhost' type='get' xmlns='jabber:client' />", | ||||||
|         '', |         '', | ||||||
|         ignoreId: true, |         ignoreId: true, | ||||||
|       ), |       ), | ||||||
| @ -734,7 +723,7 @@ void main() { | |||||||
|         "<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />", |         "<resumed xmlns='urn:xmpp:sm:3' h='id-1' h='12' />", | ||||||
|       ), |       ), | ||||||
|       StanzaExpectation( |       StanzaExpectation( | ||||||
|         "<iq to='localhost' type='get' from='polynomdivision@test.server/abc123' xmlns='jabber:client' />", |         "<iq to='localhost' type='get' xmlns='jabber:client' />", | ||||||
|         '', |         '', | ||||||
|         ignoreId: true, |         ignoreId: true, | ||||||
|       ), |       ), | ||||||
| @ -776,7 +765,11 @@ void main() { | |||||||
| 
 | 
 | ||||||
|     // Send a bogus stanza |     // Send a bogus stanza | ||||||
|     unawaited( |     unawaited( | ||||||
|       conn.sendStanza(Stanza.iq(to: 'localhost', type: 'get')), |       conn.sendStanza( | ||||||
|  |         StanzaDetails( | ||||||
|  |           Stanza.iq(to: 'localhost', type: 'get'), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     await Future<void>.delayed(const Duration(seconds: 5)); |     await Future<void>.delayed(const Duration(seconds: 5)); | ||||||
|  | |||||||
| @ -9,17 +9,9 @@ void main() { | |||||||
|   test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", |   test("Test if we're vulnerable against CVE-2020-26547 style vulnerabilities", | ||||||
|       () async { |       () async { | ||||||
|     final attributes = XmppManagerAttributes( |     final attributes = XmppManagerAttributes( | ||||||
|       sendStanza: ( |       sendStanza: (StanzaDetails details) async { | ||||||
|         stanza, { |  | ||||||
|         StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|         bool addId = true, |  | ||||||
|         bool retransmitted = false, |  | ||||||
|         bool awaitable = true, |  | ||||||
|         bool encrypted = false, |  | ||||||
|         bool forceEncryption = false, |  | ||||||
|       }) async { |  | ||||||
|         // ignore: avoid_print |         // ignore: avoid_print | ||||||
|         print('==> ${stanza.toXml()}'); |         print('==> ${details.stanza.toXml()}'); | ||||||
|         return XMLNode(tag: 'iq', attributes: {'type': 'result'}); |         return XMLNode(tag: 'iq', attributes: {'type': 'result'}); | ||||||
|       }, |       }, | ||||||
|       sendNonza: (nonza) {}, |       sendNonza: (nonza) {}, | ||||||
|  | |||||||
| @ -39,7 +39,6 @@ void main() { | |||||||
|           XmppManagerAttributes( |           XmppManagerAttributes( | ||||||
|             sendStanza: ( |             sendStanza: ( | ||||||
|               _, { |               _, { | ||||||
|               StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|               bool addId = true, |               bool addId = true, | ||||||
|               bool retransmitted = false, |               bool retransmitted = false, | ||||||
|               bool awaitable = true, |               bool awaitable = true, | ||||||
| @ -78,7 +77,6 @@ void main() { | |||||||
|           XmppManagerAttributes( |           XmppManagerAttributes( | ||||||
|             sendStanza: ( |             sendStanza: ( | ||||||
|               _, { |               _, { | ||||||
|               StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|               bool addId = true, |               bool addId = true, | ||||||
|               bool retransmitted = false, |               bool retransmitted = false, | ||||||
|               bool awaitable = true, |               bool awaitable = true, | ||||||
|  | |||||||
| @ -4,6 +4,32 @@ import 'package:test/test.dart'; | |||||||
| import 'helpers/logging.dart'; | import 'helpers/logging.dart'; | ||||||
| import 'helpers/xmpp.dart'; | import 'helpers/xmpp.dart'; | ||||||
| 
 | 
 | ||||||
|  | class StubConnectivityManager extends ConnectivityManager { | ||||||
|  |   bool _hasConnection = true; | ||||||
|  | 
 | ||||||
|  |   Completer<void> _goingOnlineCompleter = Completer<void>(); | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<bool> hasConnection() async => _hasConnection; | ||||||
|  | 
 | ||||||
|  |   @override | ||||||
|  |   Future<void> waitForConnection() async { | ||||||
|  |     if (!_hasConnection) { | ||||||
|  |       await _goingOnlineCompleter.future; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void goOffline() { | ||||||
|  |     _hasConnection = false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   void goOnline() { | ||||||
|  |     _hasConnection = true; | ||||||
|  |     _goingOnlineCompleter.complete(); | ||||||
|  |     _goingOnlineCompleter = Completer<void>(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Returns true if the roster manager triggeres an event for a given stanza | /// Returns true if the roster manager triggeres an event for a given stanza | ||||||
| Future<bool> testRosterManager( | Future<bool> testRosterManager( | ||||||
|   String bareJid, |   String bareJid, | ||||||
| @ -16,7 +42,6 @@ Future<bool> testRosterManager( | |||||||
|       XmppManagerAttributes( |       XmppManagerAttributes( | ||||||
|         sendStanza: ( |         sendStanza: ( | ||||||
|           _, { |           _, { | ||||||
|           StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|           bool addId = true, |           bool addId = true, | ||||||
|           bool retransmitted = false, |           bool retransmitted = false, | ||||||
|           bool awaitable = true, |           bool awaitable = true, | ||||||
| @ -131,11 +156,7 @@ void main() { | |||||||
|         password: 'aaaa', |         password: 'aaaa', | ||||||
|       ); |       ); | ||||||
|     await conn.registerManagers([ |     await conn.registerManagers([ | ||||||
|       PresenceManager(), |  | ||||||
|       RosterManager(TestingRosterStateManager('', [])), |  | ||||||
|       DiscoManager([]), |  | ||||||
|       StreamManagementManager(), |       StreamManagementManager(), | ||||||
|       EntityCapabilitiesManager('http://moxxmpp.example'), |  | ||||||
|     ]); |     ]); | ||||||
|     await conn.registerFeatureNegotiators([ |     await conn.registerFeatureNegotiators([ | ||||||
|       SaslPlainNegotiator(), |       SaslPlainNegotiator(), | ||||||
| @ -271,7 +292,6 @@ void main() { | |||||||
|           XmppManagerAttributes( |           XmppManagerAttributes( | ||||||
|             sendStanza: ( |             sendStanza: ( | ||||||
|               _, { |               _, { | ||||||
|               StanzaFromType addFrom = StanzaFromType.full, |  | ||||||
|               bool addId = true, |               bool addId = true, | ||||||
|               bool retransmitted = false, |               bool retransmitted = false, | ||||||
|               bool awaitable = true, |               bool awaitable = true, | ||||||
| @ -625,4 +645,152 @@ void main() { | |||||||
|       true, |       true, | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   test('Test sending stanzas while offline', () async { | ||||||
|  |     final fakeSocket = StubTCPSocket( | ||||||
|  |       [ | ||||||
|  |         StringExpectation( | ||||||
|  |           "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>", | ||||||
|  |           ''' | ||||||
|  | <stream:stream | ||||||
|  |     xmlns="jabber:client" | ||||||
|  |     version="1.0" | ||||||
|  |     xmlns:stream="http://etherx.jabber.org/streams" | ||||||
|  |     from="test.server" | ||||||
|  |     xml:lang="en"> | ||||||
|  |   <stream:features xmlns="http://etherx.jabber.org/streams"> | ||||||
|  |     <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> | ||||||
|  |       <mechanism>PLAIN</mechanism> | ||||||
|  |     </mechanisms> | ||||||
|  |   </stream:features>''', | ||||||
|  |         ), | ||||||
|  |         StringExpectation( | ||||||
|  |           "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", | ||||||
|  |           '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />', | ||||||
|  |         ), | ||||||
|  |         StringExpectation( | ||||||
|  |           "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>", | ||||||
|  |           ''' | ||||||
|  | <stream:stream | ||||||
|  |     xmlns="jabber:client" | ||||||
|  |     version="1.0" | ||||||
|  |     xmlns:stream="http://etherx.jabber.org/streams" | ||||||
|  |     from="test.server" | ||||||
|  |     xml:lang="en"> | ||||||
|  |   <stream:features xmlns="http://etherx.jabber.org/streams"> | ||||||
|  |     <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> | ||||||
|  |       <required/> | ||||||
|  |     </bind> | ||||||
|  |     <session xmlns="urn:ietf:params:xml:ns:xmpp-session"> | ||||||
|  |       <optional/> | ||||||
|  |     </session> | ||||||
|  |     <csi xmlns="urn:xmpp:csi:0"/> | ||||||
|  |     <sm xmlns="urn:xmpp:sm:3"/> | ||||||
|  |   </stream:features> | ||||||
|  | ''', | ||||||
|  |         ), | ||||||
|  |         StanzaExpectation( | ||||||
|  |           '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', | ||||||
|  |           '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', | ||||||
|  |           ignoreId: true, | ||||||
|  |         ), | ||||||
|  |         StringExpectation( | ||||||
|  |           "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>", | ||||||
|  |           ''' | ||||||
|  | <stream:stream | ||||||
|  |     xmlns="jabber:client" | ||||||
|  |     version="1.0" | ||||||
|  |     xmlns:stream="http://etherx.jabber.org/streams" | ||||||
|  |     from="test.server" | ||||||
|  |     xml:lang="en"> | ||||||
|  |   <stream:features xmlns="http://etherx.jabber.org/streams"> | ||||||
|  |     <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> | ||||||
|  |       <mechanism>PLAIN</mechanism> | ||||||
|  |     </mechanisms> | ||||||
|  |   </stream:features>''', | ||||||
|  |         ), | ||||||
|  |         StringExpectation( | ||||||
|  |           "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>", | ||||||
|  |           '<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />', | ||||||
|  |         ), | ||||||
|  |         StringExpectation( | ||||||
|  |           "<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>", | ||||||
|  |           ''' | ||||||
|  | <stream:stream | ||||||
|  |     xmlns="jabber:client" | ||||||
|  |     version="1.0" | ||||||
|  |     xmlns:stream="http://etherx.jabber.org/streams" | ||||||
|  |     from="test.server" | ||||||
|  |     xml:lang="en"> | ||||||
|  |   <stream:features xmlns="http://etherx.jabber.org/streams"> | ||||||
|  |     <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"> | ||||||
|  |       <required/> | ||||||
|  |     </bind> | ||||||
|  |     <session xmlns="urn:ietf:params:xml:ns:xmpp-session"> | ||||||
|  |       <optional/> | ||||||
|  |     </session> | ||||||
|  |     <csi xmlns="urn:xmpp:csi:0"/> | ||||||
|  |     <sm xmlns="urn:xmpp:sm:3"/> | ||||||
|  |   </stream:features> | ||||||
|  | ''', | ||||||
|  |         ), | ||||||
|  |         StanzaExpectation( | ||||||
|  |           '<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>', | ||||||
|  |           '<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>', | ||||||
|  |           ignoreId: true, | ||||||
|  |         ), | ||||||
|  |         StanzaExpectation( | ||||||
|  |           '<iq xmlns="jabber:client" type="get" id="abc123"></iq>', | ||||||
|  |           '<iq xmlns="jabber:client" type="result" id="abc123"></iq>', | ||||||
|  |           ignoreId: true, | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |     final connectivity = StubConnectivityManager(); | ||||||
|  |     final conn = XmppConnection( | ||||||
|  |       TestingReconnectionPolicy(), | ||||||
|  |       connectivity, | ||||||
|  |       ClientToServerNegotiator(), | ||||||
|  |       fakeSocket, | ||||||
|  |     )..connectionSettings = ConnectionSettings( | ||||||
|  |         jid: JID.fromString('polynomdivision@test.server'), | ||||||
|  |         password: 'aaaa', | ||||||
|  |       ); | ||||||
|  |     await conn.registerFeatureNegotiators([ | ||||||
|  |       SaslPlainNegotiator(), | ||||||
|  |       SaslScramNegotiator(10, '', '', ScramHashType.sha512), | ||||||
|  |       ResourceBindingNegotiator(), | ||||||
|  |     ]); | ||||||
|  | 
 | ||||||
|  |     await conn.connect( | ||||||
|  |       waitUntilLogin: true, | ||||||
|  |     ); | ||||||
|  |     expect(fakeSocket.getState(), 4); | ||||||
|  | 
 | ||||||
|  |     // Fake going offline | ||||||
|  |     connectivity.goOffline(); | ||||||
|  |     await conn.handleSocketEvent( | ||||||
|  |       XmppSocketClosureEvent(false), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     // Send a stanza while offline | ||||||
|  |     final stanzaFuture = conn.sendStanza( | ||||||
|  |       StanzaDetails( | ||||||
|  |         Stanza.iq( | ||||||
|  |           id: 'abc123', | ||||||
|  |           type: 'get', | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     // Come online again | ||||||
|  |     connectivity.goOnline(); | ||||||
|  |     await conn.connect( | ||||||
|  |       waitUntilLogin: true, | ||||||
|  |     ); | ||||||
|  |     await Future<void>.delayed(const Duration(seconds: 6)); | ||||||
|  | 
 | ||||||
|  |     expect(fakeSocket.getState(), 9); | ||||||
|  |     expect(await stanzaFuture != null, true); | ||||||
|  |   }); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user