Merge pull request 'Stanza send queue' (#40) from feat/send-queue into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/40
This commit is contained in:
commit
c3be199cca
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,
|
||||||
if (addFrom != StanzaFromType.none &&
|
),
|
||||||
(stanza_.from == null || stanza_.from == '')) {
|
|
||||||
switch (addFrom) {
|
|
||||||
case StanzaFromType.full:
|
|
||||||
{
|
|
||||||
stanza_ = stanza_.copyWith(
|
|
||||||
from: _getJidWithResource().toString(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return completer?.future;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case StanzaFromType.bare:
|
Future<void> _sendStanzaImpl(StanzaQueueEntry entry) async {
|
||||||
{
|
final details = entry.details;
|
||||||
stanza_ = stanza_.copyWith(
|
var newStanza = details.stanza;
|
||||||
from: connectionSettings.jid.toBare().toString(),
|
|
||||||
);
|
// Generate an id, if requested
|
||||||
|
if (details.addId && (newStanza.id == null || newStanza.id == '')) {
|
||||||
|
newStanza = newStanza.copyWith(id: generateId());
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case StanzaFromType.none:
|
// NOTE: Originally, we handled adding a "from" attribute to the stanza here.
|
||||||
break;
|
// However, this is not neccessary as RFC 6120 states:
|
||||||
}
|
//
|
||||||
}
|
// > 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' 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
|
||||||
|
// > the connected resource that generated the stanza (see
|
||||||
|
// > Section 4.3.6), or the bare JID (<localpart@domainpart>) in the
|
||||||
|
// > case of subscription-related presence stanzas (see [XMPP-IM]).
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Add the correct stanza namespace
|
||||||
|
newStanza = newStanza.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(
|
|
||||||
|
// Resolve the future, if one was given.
|
||||||
|
if (details.awaitable) {
|
||||||
|
entry.completer!.complete(
|
||||||
|
Stanza(
|
||||||
tag: data.stanza.tag,
|
tag: data.stanza.tag,
|
||||||
to: data.stanza.from,
|
to: data.stanza.from,
|
||||||
from: data.stanza.to,
|
from: data.stanza.to,
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
'type': 'error',
|
'type': 'error',
|
||||||
...data.stanza.id != null
|
if (data.stanza.id != null) 'id': data.stanza.id!,
|
||||||
? {
|
|
||||||
'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(
|
||||||
|
StanzaDetails(
|
||||||
stanza,
|
stanza,
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
forceEncryption: data.encrypted,
|
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(
|
||||||
|
StanzaDetails(
|
||||||
stanza,
|
stanza,
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
forceEncryption: data.encrypted,
|
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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
from: attrs.getFullJID().toString(),
|
|
||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
addId: 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unavailable',
|
type: 'unavailable',
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.full,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a subscription request to [to].
|
/// Sends a subscription request to [to].
|
||||||
void sendSubscriptionRequest(String to) {
|
void sendSubscriptionRequest(String to) {
|
||||||
getAttributes().sendStanza(
|
getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'subscribe',
|
type: 'subscribe',
|
||||||
to: to,
|
to: to,
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.none,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an unsubscription request to [to].
|
/// Sends an unsubscription request to [to].
|
||||||
void sendUnsubscriptionRequest(String to) {
|
void sendUnsubscriptionRequest(String to) {
|
||||||
getAttributes().sendStanza(
|
getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unsubscribe',
|
type: 'unsubscribe',
|
||||||
to: to,
|
to: to,
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.none,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'subscribed',
|
type: 'subscribed',
|
||||||
to: to,
|
to: to,
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.none,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unsubscribed',
|
type: 'unsubscribed',
|
||||||
to: to,
|
to: to,
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.none,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
children: [
|
children: [
|
||||||
query,
|
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,7 +260,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
children: [
|
children: [
|
||||||
@ -271,7 +274,8 @@ class RosterManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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,7 +300,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -317,10 +322,11 @@ class RosterManager extends XmppManagerBase {
|
|||||||
.toList(),
|
.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,7 +340,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -353,7 +360,8 @@ class RosterManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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(
|
||||||
|
StanzaDetails(
|
||||||
buildDiscoInfoQueryStanza(entity, node),
|
buildDiscoInfoQueryStanza(entity, node),
|
||||||
encrypted: !shouldEncrypt,
|
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(
|
||||||
|
StanzaDetails(
|
||||||
buildDiscoItemsQueryStanza(entity, node: node),
|
buildDiscoItemsQueryStanza(entity, node: node),
|
||||||
encrypted: !shouldEncrypt,
|
encrypted: !shouldEncrypt,
|
||||||
) as Stanza;
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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,7 +103,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
to: jid,
|
to: jid,
|
||||||
type: 'get',
|
type: 'get',
|
||||||
@ -115,7 +116,8 @@ class VCardManager extends XmppManagerBase {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
return Result(UnknownVCardError());
|
return Result(UnknownVCardError());
|
||||||
|
@ -181,7 +181,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid,
|
||||||
@ -201,7 +202,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
return Result(UnknownPubSubError());
|
return Result(UnknownPubSubError());
|
||||||
@ -222,7 +224,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid,
|
||||||
@ -242,7 +245,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
return Result(UnknownPubSubError());
|
return Result(UnknownPubSubError());
|
||||||
@ -293,7 +297,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid.toString(),
|
to: jid.toString(),
|
||||||
@ -324,7 +329,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
final error = getPubSubError(result);
|
final error = getPubSubError(result);
|
||||||
|
|
||||||
@ -395,7 +401,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
String jid,
|
String jid,
|
||||||
String node,
|
String node,
|
||||||
) async {
|
) async {
|
||||||
final result = await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid,
|
to: jid,
|
||||||
@ -404,12 +411,16 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
xmlns: pubsubXmlns,
|
xmlns: pubsubXmlns,
|
||||||
children: [
|
children: [
|
||||||
XMLNode(tag: 'items', attributes: <String, String>{'node': node}),
|
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,7 +447,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
String node,
|
String node,
|
||||||
String id,
|
String id,
|
||||||
) async {
|
) async {
|
||||||
final result = await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid,
|
to: jid,
|
||||||
@ -459,7 +471,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
return Result(getPubSubError(result));
|
return Result(getPubSubError(result));
|
||||||
@ -488,7 +501,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid.toString(),
|
to: jid.toString(),
|
||||||
@ -507,12 +521,14 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid.toString(),
|
to: jid.toString(),
|
||||||
@ -534,7 +550,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
if (submit.attributes['type'] != 'result') {
|
if (submit.attributes['type'] != 'result') {
|
||||||
return Result(getPubSubError(form));
|
return Result(getPubSubError(form));
|
||||||
}
|
}
|
||||||
@ -543,7 +560,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: host.toString(),
|
to: host.toString(),
|
||||||
@ -562,9 +580,10 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
) 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,7 +596,8 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
String node,
|
String node,
|
||||||
String itemId,
|
String itemId,
|
||||||
) async {
|
) async {
|
||||||
final request = await getAttributes().sendStanza(
|
final request = (await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: host.toString(),
|
to: host.toString(),
|
||||||
@ -604,9 +624,10 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
) 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.message(
|
Stanza.message(
|
||||||
to: to,
|
to: to,
|
||||||
type: messageType,
|
type: messageType,
|
||||||
children: [XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns)],
|
children: [
|
||||||
|
XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -112,13 +113,15 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -128,7 +131,8 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
return result.attributes['type'] == 'result';
|
return result.attributes['type'] == 'result';
|
||||||
}
|
}
|
||||||
@ -136,7 +140,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -154,13 +159,15 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
children: [
|
children: [
|
||||||
@ -170,7 +177,8 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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,7 +110,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
to: attrs.getFullJID().toBare().toString(),
|
to: attrs.getFullJID().toBare().toString(),
|
||||||
type: 'set',
|
type: 'set',
|
||||||
@ -122,9 +122,8 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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,7 +141,8 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
children: [
|
children: [
|
||||||
@ -152,9 +152,8 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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,7 +149,8 @@ class HttpFileUploadManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final response = await attrs.sendStanza(
|
final response = (await attrs.sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
to: _entityJid.toString(),
|
to: _entityJid.toString(),
|
||||||
type: 'get',
|
type: 'get',
|
||||||
@ -165,7 +166,8 @@ class HttpFileUploadManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
))!;
|
||||||
|
|
||||||
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,6 +262,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
String toJid,
|
String toJid,
|
||||||
) async {
|
) async {
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.message(
|
Stanza.message(
|
||||||
to: toJid,
|
to: toJid,
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
@ -280,6 +281,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
encrypted: true,
|
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