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
 | 
			
		||||
`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
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    stanza_ = stanza_.copyWith(
 | 
			
		||||
      xmlns: _negotiationsHandler.getStanzaNamespace(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    _log.fine('Running pre stanza handlers..');
 | 
			
		||||
    final data = await _runOutgoingPreStanzaHandlers(
 | 
			
		||||
@ -731,6 +734,7 @@ class XmppConnection {
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
    if (!incomingHandlers.done) {
 | 
			
		||||
      _log.warning('Returning error for unhandled stanza');
 | 
			
		||||
      await handleUnhandledStanza(this, incomingPreHandlers);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -81,6 +81,9 @@ abstract class NegotiationsHandler {
 | 
			
		||||
    log = Logger(toString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Returns the xmlns attribute that stanzas should have.
 | 
			
		||||
  String getStanzaNamespace();
 | 
			
		||||
 | 
			
		||||
  /// Registers the negotiator [negotiator] against this negotiations handler.
 | 
			
		||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,9 @@ class ClientToServerNegotiator extends NegotiationsHandler {
 | 
			
		||||
  /// The currently active negotiator.
 | 
			
		||||
  XmppFeatureNegotiatorBase? _currentNegotiator;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String getStanzaNamespace() => stanzaXmlns;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
 | 
			
		||||
    negotiators[negotiator.id] = negotiator;
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,9 @@ class ComponentToServerNegotiator extends NegotiationsHandler {
 | 
			
		||||
  /// The state the negotiation handler is currently in
 | 
			
		||||
  ComponentToServerState _state = ComponentToServerState.idle;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String getStanzaNamespace() => componentAcceptXmlns;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,86 +4,106 @@ import 'package:moxxmpp/src/namespaces.dart';
 | 
			
		||||
import 'package:moxxmpp/src/stanza.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 {
 | 
			
		||||
  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].
 | 
			
		||||
  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) {
 | 
			
		||||
    var matches = false;
 | 
			
		||||
 | 
			
		||||
    var matches = true;
 | 
			
		||||
    if (nonzaTag == null && nonzaXmlns == null) {
 | 
			
		||||
      matches = true;
 | 
			
		||||
      return true;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (nonzaXmlns != null) {
 | 
			
		||||
        matches &= node.attributes['xmlns'] == nonzaXmlns;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    if (nonzaXmlns != null && nonzaTag != null) {
 | 
			
		||||
      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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class NonzaHandler extends Handler {
 | 
			
		||||
  NonzaHandler({
 | 
			
		||||
    required this.callback,
 | 
			
		||||
    String? nonzaTag,
 | 
			
		||||
    String? nonzaXmlns,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          false,
 | 
			
		||||
          nonzaTag: nonzaTag,
 | 
			
		||||
          nonzaXmlns: nonzaXmlns,
 | 
			
		||||
        );
 | 
			
		||||
  final Future<bool> Function(XMLNode) callback;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A Handler that only matches stanzas.
 | 
			
		||||
class StanzaHandler extends Handler {
 | 
			
		||||
  StanzaHandler({
 | 
			
		||||
    required this.callback,
 | 
			
		||||
    this.tagXmlns,
 | 
			
		||||
    this.tagName,
 | 
			
		||||
    this.priority = 0,
 | 
			
		||||
    String? stanzaTag,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          true,
 | 
			
		||||
          nonzaTag: stanzaTag,
 | 
			
		||||
          nonzaXmlns: stanzaXmlns,
 | 
			
		||||
        );
 | 
			
		||||
    this.stanzaTag,
 | 
			
		||||
    this.xmlns = stanzaXmlns,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /// If specified, then the stanza must contain a direct child with a tag equal to
 | 
			
		||||
  /// [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;
 | 
			
		||||
 | 
			
		||||
  /// 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;
 | 
			
		||||
 | 
			
		||||
  /// The function to call when a stanza matches the description.
 | 
			
		||||
  final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool matches(XMLNode node) {
 | 
			
		||||
    var matches = super.matches(node);
 | 
			
		||||
 | 
			
		||||
    if (matches == false) {
 | 
			
		||||
      return false;
 | 
			
		||||
    var matches = ['iq', 'message', 'presence'].contains(node.tag);
 | 
			
		||||
    if (stanzaTag != null) {
 | 
			
		||||
      matches &= node.tag == stanzaTag;
 | 
			
		||||
    }
 | 
			
		||||
    if (xmlns != null) {
 | 
			
		||||
      matches &= node.xmlns == xmlns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (tagName != null) {
 | 
			
		||||
      final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
 | 
			
		||||
      matches &= firstTag != null;
 | 
			
		||||
 | 
			
		||||
      matches = firstTag != null;
 | 
			
		||||
    } else if (tagXmlns != null) {
 | 
			
		||||
      return listContains(
 | 
			
		||||
        node.children,
 | 
			
		||||
        (XMLNode node_) =>
 | 
			
		||||
            node_.attributes.containsKey('xmlns') &&
 | 
			
		||||
            node_.attributes['xmlns'] == tagXmlns,
 | 
			
		||||
      );
 | 
			
		||||
      if (tagXmlns != null) {
 | 
			
		||||
        matches &= firstTag?.xmlns == tagXmlns;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    if (tagName == null && tagXmlns == null) {
 | 
			
		||||
      matches = true;
 | 
			
		||||
    } else if (tagXmlns != null) {
 | 
			
		||||
      matches &= listContains(
 | 
			
		||||
        node.children,
 | 
			
		||||
        (XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return matches;
 | 
			
		||||
 | 
			
		||||
@ -107,11 +107,13 @@ class PresenceManager extends XmppManagerBase {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final attrs = getAttributes();
 | 
			
		||||
    attrs.sendNonza(
 | 
			
		||||
    await attrs.sendStanza(
 | 
			
		||||
      Stanza.presence(
 | 
			
		||||
        from: attrs.getFullJID().toString(),
 | 
			
		||||
        children: children,
 | 
			
		||||
      ),
 | 
			
		||||
      awaitable: false,
 | 
			
		||||
      addId: false,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
    List<XMLNode> children = const [],
 | 
			
		||||
    required String tag,
 | 
			
		||||
    Map<String, String> attributes = const {},
 | 
			
		||||
    String? xmlns,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          tag: tag,
 | 
			
		||||
          attributes: <String, dynamic>{
 | 
			
		||||
@ -45,7 +46,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
            ...from != null
 | 
			
		||||
                ? <String, dynamic>{'from': from}
 | 
			
		||||
                : <String, dynamic>{},
 | 
			
		||||
            'xmlns': stanzaXmlns
 | 
			
		||||
            if (xmlns != null) 'xmlns': xmlns,
 | 
			
		||||
          },
 | 
			
		||||
          children: children,
 | 
			
		||||
        );
 | 
			
		||||
@ -57,6 +58,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
    String? id,
 | 
			
		||||
    List<XMLNode> children = const [],
 | 
			
		||||
    Map<String, String>? attributes = const {},
 | 
			
		||||
    String? xmlns,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Stanza(
 | 
			
		||||
      tag: 'iq',
 | 
			
		||||
@ -64,8 +66,9 @@ class Stanza extends XMLNode {
 | 
			
		||||
      to: to,
 | 
			
		||||
      id: id,
 | 
			
		||||
      type: type,
 | 
			
		||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
			
		||||
      attributes: <String, String>{...attributes!},
 | 
			
		||||
      children: children,
 | 
			
		||||
      xmlns: xmlns,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -76,6 +79,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
    String? id,
 | 
			
		||||
    List<XMLNode> children = const [],
 | 
			
		||||
    Map<String, String>? attributes = const {},
 | 
			
		||||
    String? xmlns,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Stanza(
 | 
			
		||||
      tag: 'presence',
 | 
			
		||||
@ -83,8 +87,9 @@ class Stanza extends XMLNode {
 | 
			
		||||
      to: to,
 | 
			
		||||
      id: id,
 | 
			
		||||
      type: type,
 | 
			
		||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
			
		||||
      attributes: <String, String>{...attributes!},
 | 
			
		||||
      children: children,
 | 
			
		||||
      xmlns: xmlns,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  factory Stanza.message({
 | 
			
		||||
@ -94,6 +99,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
    String? id,
 | 
			
		||||
    List<XMLNode> children = const [],
 | 
			
		||||
    Map<String, String>? attributes = const {},
 | 
			
		||||
    String? xmlns,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Stanza(
 | 
			
		||||
      tag: 'message',
 | 
			
		||||
@ -101,8 +107,9 @@ class Stanza extends XMLNode {
 | 
			
		||||
      to: to,
 | 
			
		||||
      id: id,
 | 
			
		||||
      type: type,
 | 
			
		||||
      attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
 | 
			
		||||
      attributes: <String, String>{...attributes!},
 | 
			
		||||
      children: children,
 | 
			
		||||
      xmlns: xmlns,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -134,6 +141,7 @@ class Stanza extends XMLNode {
 | 
			
		||||
    String? to,
 | 
			
		||||
    String? type,
 | 
			
		||||
    List<XMLNode>? children,
 | 
			
		||||
    String? xmlns,
 | 
			
		||||
  }) {
 | 
			
		||||
    return Stanza(
 | 
			
		||||
      tag: tag,
 | 
			
		||||
@ -142,6 +150,10 @@ class Stanza extends XMLNode {
 | 
			
		||||
      id: id ?? this.id,
 | 
			
		||||
      type: type ?? this.type,
 | 
			
		||||
      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(
 | 
			
		||||
  children: [XMLNode.xmlns(tag: 'tag', xmlns: 'owo')],
 | 
			
		||||
  xmlns: stanzaXmlns,
 | 
			
		||||
);
 | 
			
		||||
final stanza2 = Stanza.message(
 | 
			
		||||
  children: [XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')],
 | 
			
		||||
  xmlns: stanzaXmlns,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
@ -19,11 +21,17 @@ void main() {
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(handler.matches(Stanza.iq()), true);
 | 
			
		||||
    expect(handler.matches(Stanza.message()), true);
 | 
			
		||||
    expect(handler.matches(Stanza.presence()), true);
 | 
			
		||||
    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
 | 
			
		||||
    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), true);
 | 
			
		||||
    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), true);
 | 
			
		||||
    expect(handler.matches(stanza1), true);
 | 
			
		||||
    expect(handler.matches(stanza2), true);
 | 
			
		||||
    expect(
 | 
			
		||||
      handler.matches(
 | 
			
		||||
        XMLNode.xmlns(tag: 'active', xmlns: csiXmlns),
 | 
			
		||||
      ),
 | 
			
		||||
      false,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  test('xmlns matching', () {
 | 
			
		||||
    final handler = StanzaHandler(
 | 
			
		||||
@ -36,12 +44,13 @@ void main() {
 | 
			
		||||
      tagXmlns: 'owo',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(handler.matches(Stanza.iq()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.message()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.presence()), false);
 | 
			
		||||
    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(stanza1), true);
 | 
			
		||||
    expect(handler.matches(stanza2), true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('stanzaTag matching', () {
 | 
			
		||||
    var run = false;
 | 
			
		||||
    final handler = StanzaHandler(
 | 
			
		||||
@ -57,9 +66,9 @@ void main() {
 | 
			
		||||
      stanzaTag: 'iq',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(handler.matches(Stanza.iq()), true);
 | 
			
		||||
    expect(handler.matches(Stanza.message()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.presence()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
 | 
			
		||||
    expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
 | 
			
		||||
    expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
 | 
			
		||||
    expect(handler.matches(stanza1), true);
 | 
			
		||||
    expect(handler.matches(stanza2), false);
 | 
			
		||||
 | 
			
		||||
@ -74,6 +83,7 @@ void main() {
 | 
			
		||||
    );
 | 
			
		||||
    expect(run, true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('tagName matching', () {
 | 
			
		||||
    final handler = StanzaHandler(
 | 
			
		||||
      callback: (stanza, _) async => StanzaHandlerData(
 | 
			
		||||
@ -85,12 +95,13 @@ void main() {
 | 
			
		||||
      tagName: 'tag',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(handler.matches(Stanza.iq()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.message()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.presence()), false);
 | 
			
		||||
    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(stanza1), true);
 | 
			
		||||
    expect(handler.matches(stanza2), false);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('combined matching', () {
 | 
			
		||||
    final handler = StanzaHandler(
 | 
			
		||||
      callback: (stanza, _) async => StanzaHandlerData(
 | 
			
		||||
@ -104,13 +115,32 @@ void main() {
 | 
			
		||||
      tagXmlns: 'owo',
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    expect(handler.matches(Stanza.iq()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.message()), false);
 | 
			
		||||
    expect(handler.matches(Stanza.presence()), false);
 | 
			
		||||
    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(stanza1), true);
 | 
			
		||||
    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', () {
 | 
			
		||||
    final handlerList = [
 | 
			
		||||
      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>',
 | 
			
		||||
          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>",
 | 
			
		||||
          '',
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
import 'package:moxxmpp/moxxmpp.dart';
 | 
			
		||||
import 'package:test/test.dart';
 | 
			
		||||
import '../helpers/logging.dart';
 | 
			
		||||
@ -88,6 +89,7 @@ void main() {
 | 
			
		||||
  final stanza = Stanza(
 | 
			
		||||
    to: 'some.user@server.example',
 | 
			
		||||
    tag: 'message',
 | 
			
		||||
    xmlns: stanzaXmlns,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  test('Test stream with SM enablement', () async {
 | 
			
		||||
@ -388,10 +390,14 @@ void main() {
 | 
			
		||||
          "<enable xmlns='urn:xmpp:sm:3' 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>",
 | 
			
		||||
          '<iq type="result" />',
 | 
			
		||||
        ),
 | 
			
		||||
        StringExpectation(
 | 
			
		||||
          "<r xmlns='urn:xmpp:sm:3' />",
 | 
			
		||||
          "<a xmlns='urn:xmpp:sm:3' h='1' />",
 | 
			
		||||
        ),
 | 
			
		||||
        StanzaExpectation(
 | 
			
		||||
          "<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
 | 
			
		||||
          "<iq from='user@example.com' type='result' id='a' />",
 | 
			
		||||
@ -425,7 +431,8 @@ void main() {
 | 
			
		||||
        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(
 | 
			
		||||
        conn
 | 
			
		||||
@ -502,9 +509,6 @@ void main() {
 | 
			
		||||
          password: 'aaaa',
 | 
			
		||||
        );
 | 
			
		||||
      await conn.registerManagers([
 | 
			
		||||
        PresenceManager(),
 | 
			
		||||
        RosterManager(TestingRosterStateManager('', [])),
 | 
			
		||||
        DiscoManager([]),
 | 
			
		||||
        StreamManagementManager(),
 | 
			
		||||
      ]);
 | 
			
		||||
      await conn.registerFeatureNegotiators([
 | 
			
		||||
@ -523,7 +527,8 @@ void main() {
 | 
			
		||||
      await conn.connect(
 | 
			
		||||
        waitUntilLogin: true,
 | 
			
		||||
      );
 | 
			
		||||
      expect(fakeSocket.getState(), 7);
 | 
			
		||||
 | 
			
		||||
      expect(fakeSocket.getState(), 6);
 | 
			
		||||
      expect(await conn.getConnectionState(), XmppConnectionState.connected);
 | 
			
		||||
      expect(
 | 
			
		||||
        conn
 | 
			
		||||
 | 
			
		||||