fix(core): Fix components' stanza matching
@ -24,6 +24,15 @@ To run the example, make sure that Flutter is correctly set up and working. If y
 | 
				
			|||||||
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
 | 
					the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
 | 
				
			||||||
`ANDROID_AVD_HOME` are pointing to the correct directories.
 | 
					`ANDROID_AVD_HOME` are pointing to the correct directories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This repository contains 2 types of examples:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `example_flutter`: An example of using moxxmpp using Flutter
 | 
				
			||||||
 | 
					- `examples_dart`: A collection of pure Dart examples for showing different aspects of moxxmpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information, see the respective README files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See `./LICENSE`.
 | 
					See `./LICENSE`.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +0,0 @@
 | 
				
			|||||||
# melos_managed_dependency_overrides: moxxmpp,moxxmpp_socket_tcp
 | 
					 | 
				
			||||||
dependency_overrides:
 | 
					 | 
				
			||||||
  moxxmpp: 
 | 
					 | 
				
			||||||
    path: ../packages/moxxmpp
 | 
					 | 
				
			||||||
  moxxmpp_socket_tcp:
 | 
					 | 
				
			||||||
    path: ../packages/moxxmpp_socket_tcp
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B  | 
| 
		 Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B  | 
| 
		 Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B  | 
| 
		 Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB  | 
| 
		 Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										6
									
								
								examples_dart/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					# Files and directories created by pub.
 | 
				
			||||||
 | 
					.dart_tool/
 | 
				
			||||||
 | 
					.packages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Conventional directory for build output.
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
							
								
								
									
										7
									
								
								examples_dart/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					# Dart Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Run using `dart run bin/<example>.dart`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `component.dart`: Use moxxmpp to implement a component using XEP-0114.
 | 
				
			||||||
							
								
								
									
										30
									
								
								examples_dart/analysis_options.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					# This file configures the static analysis results for your project (errors,
 | 
				
			||||||
 | 
					# warnings, and lints).
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This enables the 'recommended' set of lints from `package:lints`.
 | 
				
			||||||
 | 
					# This set helps identify many issues that may lead to problems when running
 | 
				
			||||||
 | 
					# or consuming Dart code, and enforces writing Dart using a single, idiomatic
 | 
				
			||||||
 | 
					# style and format.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# If you want a smaller set of lints you can change this to specify
 | 
				
			||||||
 | 
					# 'package:lints/core.yaml'. These are just the most critical lints
 | 
				
			||||||
 | 
					# (the recommended set includes the core lints).
 | 
				
			||||||
 | 
					# The core lints are also what is used by pub.dev for scoring packages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include: package:lints/recommended.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Uncomment the following section to specify additional rules.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# linter:
 | 
				
			||||||
 | 
					#   rules:
 | 
				
			||||||
 | 
					#     - camel_case_types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# analyzer:
 | 
				
			||||||
 | 
					#   exclude:
 | 
				
			||||||
 | 
					#     - path/to/excluded/files/**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# For more information about the core and recommended set of lints, see
 | 
				
			||||||
 | 
					# https://dart.dev/go/core-lints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# For additional information about configuring this file, see
 | 
				
			||||||
 | 
					# https://dart.dev/guides/language/analysis-options
 | 
				
			||||||
							
								
								
									
										90
									
								
								examples_dart/bin/component.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import 'package:logging/logging.dart';
 | 
				
			||||||
 | 
					import 'package:moxxmpp/moxxmpp.dart';
 | 
				
			||||||
 | 
					import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestingTCPSocketWrapper extends TCPSocketWrapper {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool onBadCertificate(dynamic certificate, String domain) {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EchoMessageManager extends XmppManagerBase {
 | 
				
			||||||
 | 
					  EchoMessageManager() : super('org.moxxy.example.message');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<bool> isSupported() async => true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  List<StanzaHandler> getIncomingStanzaHandlers() => [
 | 
				
			||||||
 | 
					        StanzaHandler(
 | 
				
			||||||
 | 
					          stanzaTag: 'message',
 | 
				
			||||||
 | 
					          callback: _onMessage,
 | 
				
			||||||
 | 
					          priority: -100,
 | 
				
			||||||
 | 
					          xmlns: null,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<StanzaHandlerData> _onMessage(
 | 
				
			||||||
 | 
					    Stanza stanza,
 | 
				
			||||||
 | 
					    StanzaHandlerData state,
 | 
				
			||||||
 | 
					  ) async {
 | 
				
			||||||
 | 
					    final body = stanza.firstTag('body');
 | 
				
			||||||
 | 
					    if (body == null) return state.copyWith(done: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final bodyText = body.innerText();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await getAttributes().sendStanza(
 | 
				
			||||||
 | 
					      Stanza.message(
 | 
				
			||||||
 | 
					        to: stanza.from,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          XMLNode(
 | 
				
			||||||
 | 
					            tag: 'body',
 | 
				
			||||||
 | 
					            text: 'Hello, ${stanza.from}! You said "$bodyText"',
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      awaitable: false,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return state.copyWith(done: true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main(List<String> arguments) async {
 | 
				
			||||||
 | 
					  Logger.root.level = Level.ALL;
 | 
				
			||||||
 | 
					  Logger.root.onRecord.listen((record) {
 | 
				
			||||||
 | 
					    // ignore: avoid_print
 | 
				
			||||||
 | 
					    print(
 | 
				
			||||||
 | 
					      '[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final conn = XmppConnection(
 | 
				
			||||||
 | 
					    TestingReconnectionPolicy(),
 | 
				
			||||||
 | 
					    AlwaysConnectedConnectivityManager(),
 | 
				
			||||||
 | 
					    ComponentToServerNegotiator(),
 | 
				
			||||||
 | 
					    TestingTCPSocketWrapper(),
 | 
				
			||||||
 | 
					  )..connectionSettings = ConnectionSettings(
 | 
				
			||||||
 | 
					      jid: JID.fromString('component.localhost'),
 | 
				
			||||||
 | 
					      password: 'abc123',
 | 
				
			||||||
 | 
					      host: '127.0.0.1',
 | 
				
			||||||
 | 
					      port: 8888,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  await conn.registerManagers([
 | 
				
			||||||
 | 
					    EchoMessageManager(),
 | 
				
			||||||
 | 
					  ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final result = await conn.connect(
 | 
				
			||||||
 | 
					    waitUntilLogin: true,
 | 
				
			||||||
 | 
					    shouldReconnect: false,
 | 
				
			||||||
 | 
					    enableReconnectOnSuccess: false,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  if (result.isType<XmppError>()) {
 | 
				
			||||||
 | 
					    print('Failed to connect as component');
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Just block for some time to test the connection
 | 
				
			||||||
 | 
					  await Future<void>.delayed(const Duration(seconds: 9999));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								examples_dart/pubspec.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					name: example_dart
 | 
				
			||||||
 | 
					description: A sample command-line application.
 | 
				
			||||||
 | 
					version: 1.0.0
 | 
				
			||||||
 | 
					# homepage: https://www.example.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					environment:
 | 
				
			||||||
 | 
					  sdk: '>=2.18.0 <3.0.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dependencies:
 | 
				
			||||||
 | 
					  logging: ^1.0.2
 | 
				
			||||||
 | 
					  moxxmpp:
 | 
				
			||||||
 | 
					    hosted: https://git.polynom.me/api/packages/Moxxy/pub
 | 
				
			||||||
 | 
					    version: 0.3.0
 | 
				
			||||||
 | 
					  moxxmpp_socket_tcp:
 | 
				
			||||||
 | 
					    hosted: https://git.polynom.me/api/packages/Moxxy/pub
 | 
				
			||||||
 | 
					    version: 0.3.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dependency_overrides:
 | 
				
			||||||
 | 
					  moxxmpp:
 | 
				
			||||||
 | 
					    path: ../packages/moxxmpp
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					dev_dependencies:
 | 
				
			||||||
 | 
					  lints: ^2.0.0
 | 
				
			||||||
 | 
					  test: ^1.16.0
 | 
				
			||||||
@ -446,6 +446,9 @@ class XmppConnection {
 | 
				
			|||||||
          break;
 | 
					          break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    stanza_ = stanza_.copyWith(
 | 
				
			||||||
 | 
					      xmlns: _negotiationsHandler.getStanzaNamespace(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _log.fine('Running pre stanza handlers..');
 | 
					    _log.fine('Running pre stanza handlers..');
 | 
				
			||||||
    final data = await _runOutgoingPreStanzaHandlers(
 | 
					    final data = await _runOutgoingPreStanzaHandlers(
 | 
				
			||||||
@ -731,6 +734,7 @@ class XmppConnection {
 | 
				
			|||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (!incomingHandlers.done) {
 | 
					    if (!incomingHandlers.done) {
 | 
				
			||||||
 | 
					      _log.warning('Returning error for unhandled stanza');
 | 
				
			||||||
      await handleUnhandledStanza(this, incomingPreHandlers);
 | 
					      await handleUnhandledStanza(this, incomingPreHandlers);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -81,6 +81,9 @@ abstract class NegotiationsHandler {
 | 
				
			|||||||
    log = Logger(toString());
 | 
					    log = Logger(toString());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Returns the xmlns attribute that stanzas should have.
 | 
				
			||||||
 | 
					  String getStanzaNamespace();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /// Registers the negotiator [negotiator] against this negotiations handler.
 | 
					  /// Registers the negotiator [negotiator] against this negotiations handler.
 | 
				
			||||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
 | 
					  void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -35,6 +35,9 @@ class ClientToServerNegotiator extends NegotiationsHandler {
 | 
				
			|||||||
  /// The currently active negotiator.
 | 
					  /// The currently active negotiator.
 | 
				
			||||||
  XmppFeatureNegotiatorBase? _currentNegotiator;
 | 
					  XmppFeatureNegotiatorBase? _currentNegotiator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String getStanzaNamespace() => stanzaXmlns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
 | 
					  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
 | 
				
			||||||
    negotiators[negotiator.id] = negotiator;
 | 
					    negotiators[negotiator.id] = negotiator;
 | 
				
			||||||
 | 
				
			|||||||
@ -41,6 +41,9 @@ class ComponentToServerNegotiator extends NegotiationsHandler {
 | 
				
			|||||||
  /// The state the negotiation handler is currently in
 | 
					  /// The state the negotiation handler is currently in
 | 
				
			||||||
  ComponentToServerState _state = ComponentToServerState.idle;
 | 
					  ComponentToServerState _state = ComponentToServerState.idle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  String getStanzaNamespace() => componentAcceptXmlns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
 | 
					  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,88 +4,108 @@ import 'package:moxxmpp/src/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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A Handler is responsible for matching any kind of toplevel item in the XML stream
 | 
				
			||||||
 | 
					/// (stanzas and Nonzas). For that, its [matches] method is called. What happens
 | 
				
			||||||
 | 
					/// next depends on the subclass.
 | 
				
			||||||
 | 
					// ignore: one_member_abstracts
 | 
				
			||||||
abstract class Handler {
 | 
					abstract class Handler {
 | 
				
			||||||
  const Handler(this.matchStanzas, {this.nonzaTag, this.nonzaXmlns});
 | 
					 | 
				
			||||||
  final String? nonzaTag;
 | 
					 | 
				
			||||||
  final String? nonzaXmlns;
 | 
					 | 
				
			||||||
  final bool matchStanzas;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /// Returns true if the node matches the description provided by this [Handler].
 | 
					  /// Returns true if the node matches the description provided by this [Handler].
 | 
				
			||||||
 | 
					  bool matches(XMLNode node);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A Handler that specialises in matching Nonzas (and stanzas).
 | 
				
			||||||
 | 
					class NonzaHandler extends Handler {
 | 
				
			||||||
 | 
					  NonzaHandler({
 | 
				
			||||||
 | 
					    required this.callback,
 | 
				
			||||||
 | 
					    this.nonzaTag,
 | 
				
			||||||
 | 
					    this.nonzaXmlns,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The function to call when a nonza matches the description.
 | 
				
			||||||
 | 
					  final Future<bool> Function(XMLNode) callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The expected tag of a matching nonza.
 | 
				
			||||||
 | 
					  final String? nonzaTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // The expected xmlns attribute of a matching nonza.
 | 
				
			||||||
 | 
					  final String? nonzaXmlns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
  bool matches(XMLNode node) {
 | 
					  bool matches(XMLNode node) {
 | 
				
			||||||
    var matches = false;
 | 
					    var matches = true;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (nonzaTag == null && nonzaXmlns == null) {
 | 
					    if (nonzaTag == null && nonzaXmlns == null) {
 | 
				
			||||||
      matches = true;
 | 
					      return true;
 | 
				
			||||||
    }
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (nonzaXmlns != null) {
 | 
				
			||||||
    if (nonzaXmlns != null && nonzaTag != null) {
 | 
					        matches &= node.attributes['xmlns'] == nonzaXmlns;
 | 
				
			||||||
      matches = (node.attributes['xmlns'] ?? '') == nonzaXmlns! &&
 | 
					      }
 | 
				
			||||||
          node.tag == nonzaTag!;
 | 
					      if (nonzaTag != null) {
 | 
				
			||||||
    }
 | 
					        matches &= node.tag == nonzaTag;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    if (matchStanzas && nonzaTag == null) {
 | 
					 | 
				
			||||||
      matches = ['iq', 'presence', 'message'].contains(node.tag);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return matches;
 | 
					    return matches;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NonzaHandler extends Handler {
 | 
					/// A Handler that only matches stanzas.
 | 
				
			||||||
  NonzaHandler({
 | 
					 | 
				
			||||||
    required this.callback,
 | 
					 | 
				
			||||||
    String? nonzaTag,
 | 
					 | 
				
			||||||
    String? nonzaXmlns,
 | 
					 | 
				
			||||||
  }) : super(
 | 
					 | 
				
			||||||
          false,
 | 
					 | 
				
			||||||
          nonzaTag: nonzaTag,
 | 
					 | 
				
			||||||
          nonzaXmlns: nonzaXmlns,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
  final Future<bool> Function(XMLNode) callback;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class StanzaHandler extends Handler {
 | 
					class StanzaHandler extends Handler {
 | 
				
			||||||
  StanzaHandler({
 | 
					  StanzaHandler({
 | 
				
			||||||
    required this.callback,
 | 
					    required this.callback,
 | 
				
			||||||
    this.tagXmlns,
 | 
					    this.tagXmlns,
 | 
				
			||||||
    this.tagName,
 | 
					    this.tagName,
 | 
				
			||||||
    this.priority = 0,
 | 
					    this.priority = 0,
 | 
				
			||||||
    String? stanzaTag,
 | 
					    this.stanzaTag,
 | 
				
			||||||
  }) : super(
 | 
					    this.xmlns = stanzaXmlns,
 | 
				
			||||||
          true,
 | 
					  });
 | 
				
			||||||
          nonzaTag: stanzaTag,
 | 
					
 | 
				
			||||||
          nonzaXmlns: stanzaXmlns,
 | 
					  /// If specified, then the stanza must contain a direct child with a tag equal to
 | 
				
			||||||
        );
 | 
					  /// [tagName].
 | 
				
			||||||
  final String? tagName;
 | 
					  final String? tagName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// If specified, then the stanza must contain a direct child with a xmlns attribute
 | 
				
			||||||
 | 
					  /// equal to [tagXmlns]. If [tagName] is also non-null, then the element must also
 | 
				
			||||||
 | 
					  /// have a tag equal to [tagName].
 | 
				
			||||||
  final String? tagXmlns;
 | 
					  final String? tagXmlns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// If specified, the matching stanza must have a tag equal to [stanzaTag].
 | 
				
			||||||
 | 
					  final String? stanzaTag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// If specified, then the stanza must have a xmlns attribute equal to [xmlns].
 | 
				
			||||||
 | 
					  /// This defaults to [stanzaXmlns], but can be set to any other value or null. This
 | 
				
			||||||
 | 
					  /// is useful, for example, for components.
 | 
				
			||||||
 | 
					  final String? xmlns;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The priority after which [StanzaHandler]s are sorted.
 | 
				
			||||||
  final int priority;
 | 
					  final int priority;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The function to call when a stanza matches the description.
 | 
				
			||||||
  final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
 | 
					  final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  bool matches(XMLNode node) {
 | 
					  bool matches(XMLNode node) {
 | 
				
			||||||
    var matches = super.matches(node);
 | 
					    var matches = ['iq', 'message', 'presence'].contains(node.tag);
 | 
				
			||||||
 | 
					    if (stanzaTag != null) {
 | 
				
			||||||
    if (matches == false) {
 | 
					      matches &= node.tag == stanzaTag;
 | 
				
			||||||
      return false;
 | 
					    }
 | 
				
			||||||
 | 
					    if (xmlns != null) {
 | 
				
			||||||
 | 
					      matches &= node.xmlns == xmlns;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (tagName != null) {
 | 
					    if (tagName != null) {
 | 
				
			||||||
      final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
 | 
					      final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
 | 
				
			||||||
 | 
					      matches &= firstTag != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      matches = firstTag != null;
 | 
					      if (tagXmlns != null) {
 | 
				
			||||||
 | 
					        matches &= firstTag?.xmlns == tagXmlns;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } else if (tagXmlns != null) {
 | 
					    } else if (tagXmlns != null) {
 | 
				
			||||||
      return listContains(
 | 
					      matches &= listContains(
 | 
				
			||||||
        node.children,
 | 
					        node.children,
 | 
				
			||||||
        (XMLNode node_) =>
 | 
					        (XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
 | 
				
			||||||
            node_.attributes.containsKey('xmlns') &&
 | 
					 | 
				
			||||||
            node_.attributes['xmlns'] == tagXmlns,
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (tagName == null && tagXmlns == null) {
 | 
					 | 
				
			||||||
      matches = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return matches;
 | 
					    return matches;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -107,11 +107,13 @@ class PresenceManager extends XmppManagerBase {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final attrs = getAttributes();
 | 
					    final attrs = getAttributes();
 | 
				
			||||||
    attrs.sendNonza(
 | 
					    await attrs.sendStanza(
 | 
				
			||||||
      Stanza.presence(
 | 
					      Stanza.presence(
 | 
				
			||||||
        from: attrs.getFullJID().toString(),
 | 
					        from: attrs.getFullJID().toString(),
 | 
				
			||||||
        children: children,
 | 
					        children: children,
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
					      awaitable: false,
 | 
				
			||||||
 | 
					      addId: false,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
    List<XMLNode> children = const [],
 | 
					    List<XMLNode> children = const [],
 | 
				
			||||||
    required String tag,
 | 
					    required String tag,
 | 
				
			||||||
    Map<String, String> attributes = const {},
 | 
					    Map<String, String> attributes = const {},
 | 
				
			||||||
 | 
					    String? xmlns,
 | 
				
			||||||
  }) : super(
 | 
					  }) : super(
 | 
				
			||||||
          tag: tag,
 | 
					          tag: tag,
 | 
				
			||||||
          attributes: <String, dynamic>{
 | 
					          attributes: <String, dynamic>{
 | 
				
			||||||
@ -45,7 +46,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
            ...from != null
 | 
					            ...from != null
 | 
				
			||||||
                ? <String, dynamic>{'from': from}
 | 
					                ? <String, dynamic>{'from': from}
 | 
				
			||||||
                : <String, dynamic>{},
 | 
					                : <String, dynamic>{},
 | 
				
			||||||
            'xmlns': stanzaXmlns
 | 
					            if (xmlns != null) 'xmlns': xmlns,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          children: children,
 | 
					          children: children,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -57,6 +58,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
    String? id,
 | 
					    String? id,
 | 
				
			||||||
    List<XMLNode> children = const [],
 | 
					    List<XMLNode> children = const [],
 | 
				
			||||||
    Map<String, String>? attributes = const {},
 | 
					    Map<String, String>? attributes = const {},
 | 
				
			||||||
 | 
					    String? xmlns,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    return Stanza(
 | 
					    return Stanza(
 | 
				
			||||||
      tag: 'iq',
 | 
					      tag: 'iq',
 | 
				
			||||||
@ -64,8 +66,9 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
      to: to,
 | 
					      to: to,
 | 
				
			||||||
      id: id,
 | 
					      id: id,
 | 
				
			||||||
      type: type,
 | 
					      type: type,
 | 
				
			||||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
					      attributes: <String, String>{...attributes!},
 | 
				
			||||||
      children: children,
 | 
					      children: children,
 | 
				
			||||||
 | 
					      xmlns: xmlns,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -76,6 +79,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
    String? id,
 | 
					    String? id,
 | 
				
			||||||
    List<XMLNode> children = const [],
 | 
					    List<XMLNode> children = const [],
 | 
				
			||||||
    Map<String, String>? attributes = const {},
 | 
					    Map<String, String>? attributes = const {},
 | 
				
			||||||
 | 
					    String? xmlns,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    return Stanza(
 | 
					    return Stanza(
 | 
				
			||||||
      tag: 'presence',
 | 
					      tag: 'presence',
 | 
				
			||||||
@ -83,8 +87,9 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
      to: to,
 | 
					      to: to,
 | 
				
			||||||
      id: id,
 | 
					      id: id,
 | 
				
			||||||
      type: type,
 | 
					      type: type,
 | 
				
			||||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
					      attributes: <String, String>{...attributes!},
 | 
				
			||||||
      children: children,
 | 
					      children: children,
 | 
				
			||||||
 | 
					      xmlns: xmlns,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  factory Stanza.message({
 | 
					  factory Stanza.message({
 | 
				
			||||||
@ -94,6 +99,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
    String? id,
 | 
					    String? id,
 | 
				
			||||||
    List<XMLNode> children = const [],
 | 
					    List<XMLNode> children = const [],
 | 
				
			||||||
    Map<String, String>? attributes = const {},
 | 
					    Map<String, String>? attributes = const {},
 | 
				
			||||||
 | 
					    String? xmlns,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    return Stanza(
 | 
					    return Stanza(
 | 
				
			||||||
      tag: 'message',
 | 
					      tag: 'message',
 | 
				
			||||||
@ -101,8 +107,9 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
      to: to,
 | 
					      to: to,
 | 
				
			||||||
      id: id,
 | 
					      id: id,
 | 
				
			||||||
      type: type,
 | 
					      type: type,
 | 
				
			||||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
					      attributes: <String, String>{...attributes!},
 | 
				
			||||||
      children: children,
 | 
					      children: children,
 | 
				
			||||||
 | 
					      xmlns: xmlns,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -134,6 +141,7 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
    String? to,
 | 
					    String? to,
 | 
				
			||||||
    String? type,
 | 
					    String? type,
 | 
				
			||||||
    List<XMLNode>? children,
 | 
					    List<XMLNode>? children,
 | 
				
			||||||
 | 
					    String? xmlns,
 | 
				
			||||||
  }) {
 | 
					  }) {
 | 
				
			||||||
    return Stanza(
 | 
					    return Stanza(
 | 
				
			||||||
      tag: tag,
 | 
					      tag: tag,
 | 
				
			||||||
@ -142,6 +150,10 @@ class Stanza extends XMLNode {
 | 
				
			|||||||
      id: id ?? this.id,
 | 
					      id: id ?? this.id,
 | 
				
			||||||
      type: type ?? this.type,
 | 
					      type: type ?? this.type,
 | 
				
			||||||
      children: children ?? this.children,
 | 
					      children: children ?? this.children,
 | 
				
			||||||
 | 
					      attributes: {
 | 
				
			||||||
 | 
					        ...attributes.cast<String, String>(),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      xmlns: xmlns ?? this.xmlns,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,11 @@ import 'package:test/test.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
final stanza1 = Stanza.iq(
 | 
					final stanza1 = Stanza.iq(
 | 
				
			||||||
  children: [XMLNode.xmlns(tag: 'tag', xmlns: 'owo')],
 | 
					  children: [XMLNode.xmlns(tag: 'tag', xmlns: 'owo')],
 | 
				
			||||||
 | 
					  xmlns: stanzaXmlns,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
final stanza2 = Stanza.message(
 | 
					final stanza2 = Stanza.message(
 | 
				
			||||||
  children: [XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')],
 | 
					  children: [XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')],
 | 
				
			||||||
 | 
					  xmlns: stanzaXmlns,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
@ -19,11 +21,17 @@ void main() {
 | 
				
			|||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(handler.matches(Stanza.iq()), true);
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
 | 
				
			||||||
    expect(handler.matches(Stanza.message()), true);
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), true);
 | 
				
			||||||
    expect(handler.matches(Stanza.presence()), true);
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), true);
 | 
				
			||||||
    expect(handler.matches(stanza1), true);
 | 
					    expect(handler.matches(stanza1), true);
 | 
				
			||||||
    expect(handler.matches(stanza2), true);
 | 
					    expect(handler.matches(stanza2), true);
 | 
				
			||||||
 | 
					    expect(
 | 
				
			||||||
 | 
					      handler.matches(
 | 
				
			||||||
 | 
					        XMLNode.xmlns(tag: 'active', xmlns: csiXmlns),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      false,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  test('xmlns matching', () {
 | 
					  test('xmlns matching', () {
 | 
				
			||||||
    final handler = StanzaHandler(
 | 
					    final handler = StanzaHandler(
 | 
				
			||||||
@ -36,12 +44,13 @@ void main() {
 | 
				
			|||||||
      tagXmlns: 'owo',
 | 
					      tagXmlns: 'owo',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(handler.matches(Stanza.iq()), false);
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.message()), false);
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.presence()), false);
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(stanza1), true);
 | 
					    expect(handler.matches(stanza1), true);
 | 
				
			||||||
    expect(handler.matches(stanza2), true);
 | 
					    expect(handler.matches(stanza2), true);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('stanzaTag matching', () {
 | 
					  test('stanzaTag matching', () {
 | 
				
			||||||
    var run = false;
 | 
					    var run = false;
 | 
				
			||||||
    final handler = StanzaHandler(
 | 
					    final handler = StanzaHandler(
 | 
				
			||||||
@ -57,9 +66,9 @@ void main() {
 | 
				
			|||||||
      stanzaTag: 'iq',
 | 
					      stanzaTag: 'iq',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(handler.matches(Stanza.iq()), true);
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
 | 
				
			||||||
    expect(handler.matches(Stanza.message()), false);
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.presence()), false);
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(stanza1), true);
 | 
					    expect(handler.matches(stanza1), true);
 | 
				
			||||||
    expect(handler.matches(stanza2), false);
 | 
					    expect(handler.matches(stanza2), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,6 +83,7 @@ void main() {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    expect(run, true);
 | 
					    expect(run, true);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('tagName matching', () {
 | 
					  test('tagName matching', () {
 | 
				
			||||||
    final handler = StanzaHandler(
 | 
					    final handler = StanzaHandler(
 | 
				
			||||||
      callback: (stanza, _) async => StanzaHandlerData(
 | 
					      callback: (stanza, _) async => StanzaHandlerData(
 | 
				
			||||||
@ -85,12 +95,13 @@ void main() {
 | 
				
			|||||||
      tagName: 'tag',
 | 
					      tagName: 'tag',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(handler.matches(Stanza.iq()), false);
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.message()), false);
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.presence()), false);
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(stanza1), true);
 | 
					    expect(handler.matches(stanza1), true);
 | 
				
			||||||
    expect(handler.matches(stanza2), false);
 | 
					    expect(handler.matches(stanza2), false);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('combined matching', () {
 | 
					  test('combined matching', () {
 | 
				
			||||||
    final handler = StanzaHandler(
 | 
					    final handler = StanzaHandler(
 | 
				
			||||||
      callback: (stanza, _) async => StanzaHandlerData(
 | 
					      callback: (stanza, _) async => StanzaHandlerData(
 | 
				
			||||||
@ -104,13 +115,32 @@ void main() {
 | 
				
			|||||||
      tagXmlns: 'owo',
 | 
					      tagXmlns: 'owo',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(handler.matches(Stanza.iq()), false);
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.message()), false);
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(Stanza.presence()), false);
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
    expect(handler.matches(stanza1), true);
 | 
					    expect(handler.matches(stanza1), true);
 | 
				
			||||||
    expect(handler.matches(stanza2), false);
 | 
					    expect(handler.matches(stanza2), false);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  test('Test matching stanzas with a different xmlns', () {
 | 
				
			||||||
 | 
					    final handler = StanzaHandler(
 | 
				
			||||||
 | 
					      callback: (stanza, _) async => StanzaHandlerData(
 | 
				
			||||||
 | 
					        true,
 | 
				
			||||||
 | 
					        false,
 | 
				
			||||||
 | 
					        null,
 | 
				
			||||||
 | 
					        stanza,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      xmlns: componentAcceptXmlns,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
 | 
					    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
 | 
					    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
				
			||||||
 | 
					    expect(handler.matches(Stanza.iq(xmlns: componentAcceptXmlns)), true);
 | 
				
			||||||
 | 
					    expect(handler.matches(stanza1), false);
 | 
				
			||||||
 | 
					    expect(handler.matches(stanza2), false);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('sorting', () {
 | 
					  test('sorting', () {
 | 
				
			||||||
    final handlerList = [
 | 
					    final handlerList = [
 | 
				
			||||||
      StanzaHandler(
 | 
					      StanzaHandler(
 | 
				
			||||||
 | 
				
			|||||||
@ -56,7 +56,7 @@ void main() {
 | 
				
			|||||||
          '<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>',
 | 
					          '<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,
 | 
					          ignoreId: true,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        StringExpectation(
 | 
					        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' 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>",
 | 
				
			||||||
          '',
 | 
					          '',
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
import 'package:moxxmpp/moxxmpp.dart';
 | 
					import 'package:moxxmpp/moxxmpp.dart';
 | 
				
			||||||
import 'package:test/test.dart';
 | 
					import 'package:test/test.dart';
 | 
				
			||||||
import '../helpers/logging.dart';
 | 
					import '../helpers/logging.dart';
 | 
				
			||||||
@ -88,6 +89,7 @@ void main() {
 | 
				
			|||||||
  final stanza = Stanza(
 | 
					  final stanza = Stanza(
 | 
				
			||||||
    to: 'some.user@server.example',
 | 
					    to: 'some.user@server.example',
 | 
				
			||||||
    tag: 'message',
 | 
					    tag: 'message',
 | 
				
			||||||
 | 
					    xmlns: stanzaXmlns,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('Test stream with SM enablement', () async {
 | 
					  test('Test stream with SM enablement', () async {
 | 
				
			||||||
@ -388,10 +390,14 @@ void main() {
 | 
				
			|||||||
          "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
 | 
					          "<enable xmlns='urn:xmpp:sm:3' resume='true' />",
 | 
				
			||||||
          '<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" />',
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        StringExpectation(
 | 
					        StanzaExpectation(
 | 
				
			||||||
          "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
 | 
					          "<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
 | 
				
			||||||
          '<iq type="result" />',
 | 
					          '<iq type="result" />',
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					        StringExpectation(
 | 
				
			||||||
 | 
					          "<r xmlns='urn:xmpp:sm:3' />",
 | 
				
			||||||
 | 
					          "<a xmlns='urn:xmpp:sm:3' h='1' />",
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
        StanzaExpectation(
 | 
					        StanzaExpectation(
 | 
				
			||||||
          "<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
 | 
					          "<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
 | 
				
			||||||
          "<iq from='user@example.com' type='result' id='a' />",
 | 
					          "<iq from='user@example.com' type='result' id='a' />",
 | 
				
			||||||
@ -425,7 +431,8 @@ void main() {
 | 
				
			|||||||
        waitUntilLogin: true,
 | 
					        waitUntilLogin: true,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(fakeSocket.getState(), 6);
 | 
					      await Future<void>.delayed(const Duration(seconds: 3));
 | 
				
			||||||
 | 
					      expect(fakeSocket.getState(), 7);
 | 
				
			||||||
      expect(await conn.getConnectionState(), XmppConnectionState.connected);
 | 
					      expect(await conn.getConnectionState(), XmppConnectionState.connected);
 | 
				
			||||||
      expect(
 | 
					      expect(
 | 
				
			||||||
        conn
 | 
					        conn
 | 
				
			||||||
@ -502,9 +509,6 @@ void main() {
 | 
				
			|||||||
          password: 'aaaa',
 | 
					          password: 'aaaa',
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      await conn.registerManagers([
 | 
					      await conn.registerManagers([
 | 
				
			||||||
        PresenceManager(),
 | 
					 | 
				
			||||||
        RosterManager(TestingRosterStateManager('', [])),
 | 
					 | 
				
			||||||
        DiscoManager([]),
 | 
					 | 
				
			||||||
        StreamManagementManager(),
 | 
					        StreamManagementManager(),
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
      await conn.registerFeatureNegotiators([
 | 
					      await conn.registerFeatureNegotiators([
 | 
				
			||||||
@ -523,7 +527,8 @@ void main() {
 | 
				
			|||||||
      await conn.connect(
 | 
					      await conn.connect(
 | 
				
			||||||
        waitUntilLogin: true,
 | 
					        waitUntilLogin: true,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      expect(fakeSocket.getState(), 7);
 | 
					
 | 
				
			||||||
 | 
					      expect(fakeSocket.getState(), 6);
 | 
				
			||||||
      expect(await conn.getConnectionState(), XmppConnectionState.connected);
 | 
					      expect(await conn.getConnectionState(), XmppConnectionState.connected);
 | 
				
			||||||
      expect(
 | 
					      expect(
 | 
				
			||||||
        conn
 | 
					        conn
 | 
				
			||||||
 | 
				
			|||||||