Compare commits
5 Commits
1cc266c675
...
7e588f01b0
Author | SHA1 | Date | |
---|---|---|---|
7e588f01b0 | |||
c7c6c9dae4 | |||
c77cfc4dcd | |||
1bd61076ea | |||
bff4a6f707 |
@ -1,6 +1,7 @@
|
|||||||
library moxxmpp;
|
library moxxmpp;
|
||||||
|
|
||||||
export 'package:moxxmpp/src/connection.dart';
|
export 'package:moxxmpp/src/connection.dart';
|
||||||
|
export 'package:moxxmpp/src/connectivity.dart';
|
||||||
export 'package:moxxmpp/src/errors.dart';
|
export 'package:moxxmpp/src/errors.dart';
|
||||||
export 'package:moxxmpp/src/events.dart';
|
export 'package:moxxmpp/src/events.dart';
|
||||||
export 'package:moxxmpp/src/iq.dart';
|
export 'package:moxxmpp/src/iq.dart';
|
||||||
|
@ -4,6 +4,7 @@ import 'package:meta/meta.dart';
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/awaiter.dart';
|
import 'package:moxxmpp/src/awaiter.dart';
|
||||||
import 'package:moxxmpp/src/buffer.dart';
|
import 'package:moxxmpp/src/buffer.dart';
|
||||||
|
import 'package:moxxmpp/src/connectivity.dart';
|
||||||
import 'package:moxxmpp/src/errors.dart';
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/iq.dart';
|
import 'package:moxxmpp/src/iq.dart';
|
||||||
@ -94,22 +95,24 @@ class XmppConnectionResult {
|
|||||||
class XmppConnection {
|
class XmppConnection {
|
||||||
XmppConnection(
|
XmppConnection(
|
||||||
ReconnectionPolicy reconnectionPolicy,
|
ReconnectionPolicy reconnectionPolicy,
|
||||||
|
ConnectivityManager connectivityManager,
|
||||||
this._socket,
|
this._socket,
|
||||||
{
|
{
|
||||||
this.connectionPingDuration = const Duration(minutes: 3),
|
this.connectionPingDuration = const Duration(minutes: 3),
|
||||||
this.connectingTimeout = const Duration(minutes: 2),
|
this.connectingTimeout = const Duration(minutes: 2),
|
||||||
}
|
}
|
||||||
) : _reconnectionPolicy = reconnectionPolicy {
|
) : _reconnectionPolicy = reconnectionPolicy,
|
||||||
// Allow the reconnection policy to perform reconnections by itself
|
_connectivityManager = connectivityManager {
|
||||||
_reconnectionPolicy.register(
|
// Allow the reconnection policy to perform reconnections by itself
|
||||||
_attemptReconnection,
|
_reconnectionPolicy.register(
|
||||||
_onNetworkConnectionLost,
|
_attemptReconnection,
|
||||||
);
|
_onNetworkConnectionLost,
|
||||||
|
);
|
||||||
|
|
||||||
_socketStream = _socket.getDataStream();
|
_socketStream = _socket.getDataStream();
|
||||||
// TODO(Unknown): Handle on done
|
// TODO(Unknown): Handle on done
|
||||||
_socketStream.transform(_streamBuffer).forEach(handleXmlStream);
|
_socketStream.transform(_streamBuffer).forEach(handleXmlStream);
|
||||||
_socket.getEventStream().listen(_handleSocketEvent);
|
_socket.getEventStream().listen(_handleSocketEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -122,13 +125,16 @@ class XmppConnection {
|
|||||||
/// The data stream of the socket
|
/// The data stream of the socket
|
||||||
late final Stream<String> _socketStream;
|
late final Stream<String> _socketStream;
|
||||||
|
|
||||||
|
|
||||||
/// Connection settings
|
/// Connection settings
|
||||||
late ConnectionSettings _connectionSettings;
|
late ConnectionSettings _connectionSettings;
|
||||||
|
|
||||||
/// A policy on how to reconnect
|
/// A policy on how to reconnect
|
||||||
final ReconnectionPolicy _reconnectionPolicy;
|
final ReconnectionPolicy _reconnectionPolicy;
|
||||||
|
|
||||||
|
/// The class responsible for preventing errors on initial connection due
|
||||||
|
/// to no network.
|
||||||
|
final ConnectivityManager _connectivityManager;
|
||||||
|
|
||||||
/// A helper for handling await semantics with stanzas
|
/// A helper for handling await semantics with stanzas
|
||||||
final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter();
|
final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter();
|
||||||
|
|
||||||
@ -217,63 +223,47 @@ class XmppConnection {
|
|||||||
/// none can be found.
|
/// none can be found.
|
||||||
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) => _featureNegotiators[id] as T?;
|
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) => _featureNegotiators[id] as T?;
|
||||||
|
|
||||||
/// Registers an [XmppManagerBase] sub-class as a manager on this connection.
|
/// Registers a list of [XmppManagerBase] sub-classes as managers on this connection.
|
||||||
/// [sortHandlers] should NOT be touched. It specified if the handler priorities
|
Future<void> registerManagers(List<XmppManagerBase> managers) async {
|
||||||
/// should be set up. The only time this should be false is when called via
|
|
||||||
/// [registerManagers].
|
|
||||||
void registerManager(XmppManagerBase manager, { bool sortHandlers = true }) {
|
|
||||||
_log.finest('Registering ${manager.getId()}');
|
|
||||||
manager.register(
|
|
||||||
XmppManagerAttributes(
|
|
||||||
sendStanza: sendStanza,
|
|
||||||
sendNonza: sendRawXML,
|
|
||||||
sendEvent: _sendEvent,
|
|
||||||
getConnectionSettings: () => _connectionSettings,
|
|
||||||
getManagerById: getManagerById,
|
|
||||||
isFeatureSupported: _serverFeatures.contains,
|
|
||||||
getFullJID: () => _connectionSettings.jid.withResource(_resource),
|
|
||||||
getSocket: () => _socket,
|
|
||||||
getConnection: () => this,
|
|
||||||
getNegotiatorById: getNegotiatorById,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final id = manager.getId();
|
|
||||||
_xmppManagers[id] = manager;
|
|
||||||
|
|
||||||
if (id == discoManager) {
|
|
||||||
// NOTE: It is intentional that we do not exclude the [DiscoManager] from this
|
|
||||||
// loop. It may also register features.
|
|
||||||
for (final registeredManager in _xmppManagers.values) {
|
|
||||||
(manager as DiscoManager).addDiscoFeatures(registeredManager.getDiscoFeatures());
|
|
||||||
}
|
|
||||||
} else if (_xmppManagers.containsKey(discoManager)) {
|
|
||||||
(_xmppManagers[discoManager]! as DiscoManager).addDiscoFeatures(manager.getDiscoFeatures());
|
|
||||||
}
|
|
||||||
|
|
||||||
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
|
|
||||||
_incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers());
|
|
||||||
_outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers());
|
|
||||||
_outgoingPostStanzaHandlers.addAll(manager.getOutgoingPostStanzaHandlers());
|
|
||||||
|
|
||||||
if (sortHandlers) {
|
|
||||||
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
|
|
||||||
_incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
|
||||||
_outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
|
||||||
_outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [registerManager], but for a list of managers.
|
|
||||||
void registerManagers(List<XmppManagerBase> managers) {
|
|
||||||
for (final manager in managers) {
|
for (final manager in managers) {
|
||||||
registerManager(manager, sortHandlers: false);
|
_log.finest('Registering ${manager.getId()}');
|
||||||
|
manager.register(
|
||||||
|
XmppManagerAttributes(
|
||||||
|
sendStanza: sendStanza,
|
||||||
|
sendNonza: sendRawXML,
|
||||||
|
sendEvent: _sendEvent,
|
||||||
|
getConnectionSettings: () => _connectionSettings,
|
||||||
|
getManagerById: getManagerById,
|
||||||
|
isFeatureSupported: _serverFeatures.contains,
|
||||||
|
getFullJID: () => _connectionSettings.jid.withResource(_resource),
|
||||||
|
getSocket: () => _socket,
|
||||||
|
getConnection: () => this,
|
||||||
|
getNegotiatorById: getNegotiatorById,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final id = manager.getId();
|
||||||
|
_xmppManagers[id] = manager;
|
||||||
|
|
||||||
|
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
|
||||||
|
_incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers());
|
||||||
|
_outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers());
|
||||||
|
_outgoingPostStanzaHandlers.addAll(manager.getOutgoingPostStanzaHandlers());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort them
|
// Sort them
|
||||||
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||||
|
_incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||||
_outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||||
_outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||||
|
|
||||||
|
// Run the post register callbacks
|
||||||
|
for (final manager in _xmppManagers.values) {
|
||||||
|
if (!manager.initialized) {
|
||||||
|
_log.finest('Running post-registration callback for ${manager.getName()}');
|
||||||
|
await manager.postRegisterCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a list of negotiator with the connection.
|
/// Register a list of negotiator with the connection.
|
||||||
@ -378,7 +368,7 @@ class XmppConnection {
|
|||||||
// Connect again
|
// Connect again
|
||||||
// ignore: cascade_invocations
|
// ignore: cascade_invocations
|
||||||
_log.finest('Calling connect() from _attemptReconnection');
|
_log.finest('Calling connect() from _attemptReconnection');
|
||||||
await connect();
|
await connect(waitForConnection: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a stream ending error has occurred
|
/// Called when a stream ending error has occurred
|
||||||
@ -401,7 +391,11 @@ class XmppConnection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _setConnectionState(XmppConnectionState.error);
|
if (await _connectivityManager.hasConnection()) {
|
||||||
|
await _setConnectionState(XmppConnectionState.error);
|
||||||
|
} else {
|
||||||
|
await _setConnectionState(XmppConnectionState.notConnected);
|
||||||
|
}
|
||||||
await _reconnectionPolicy.onFailure();
|
await _reconnectionPolicy.onFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,7 +826,6 @@ class XmppConnection {
|
|||||||
if (_isMandatoryNegotiationDone(_streamFeatures) && !_isNegotiationPossible(_streamFeatures)) {
|
if (_isMandatoryNegotiationDone(_streamFeatures) && !_isNegotiationPossible(_streamFeatures)) {
|
||||||
_log.finest('Negotiations done!');
|
_log.finest('Negotiations done!');
|
||||||
_updateRoutingState(RoutingState.handleStanzas);
|
_updateRoutingState(RoutingState.handleStanzas);
|
||||||
await _reconnectionPolicy.onSuccess();
|
|
||||||
await _resetIsConnectionRunning();
|
await _resetIsConnectionRunning();
|
||||||
await _onNegotiationsDone();
|
await _onNegotiationsDone();
|
||||||
} else {
|
} else {
|
||||||
@ -857,7 +850,6 @@ class XmppConnection {
|
|||||||
_log.finest('Negotiations done!');
|
_log.finest('Negotiations done!');
|
||||||
|
|
||||||
_updateRoutingState(RoutingState.handleStanzas);
|
_updateRoutingState(RoutingState.handleStanzas);
|
||||||
await _reconnectionPolicy.onSuccess();
|
|
||||||
await _resetIsConnectionRunning();
|
await _resetIsConnectionRunning();
|
||||||
await _onNegotiationsDone();
|
await _onNegotiationsDone();
|
||||||
} else {
|
} else {
|
||||||
@ -875,7 +867,6 @@ class XmppConnection {
|
|||||||
_log.finest('Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!');
|
_log.finest('Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!');
|
||||||
|
|
||||||
_updateRoutingState(RoutingState.handleStanzas);
|
_updateRoutingState(RoutingState.handleStanzas);
|
||||||
await _reconnectionPolicy.onSuccess();
|
|
||||||
await _resetIsConnectionRunning();
|
await _resetIsConnectionRunning();
|
||||||
await _onNegotiationsDone();
|
await _onNegotiationsDone();
|
||||||
break;
|
break;
|
||||||
@ -987,7 +978,7 @@ class XmppConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _disconnect({required XmppConnectionState state, bool triggeredByUser = true}) async {
|
Future<void> _disconnect({required XmppConnectionState state, bool triggeredByUser = true}) async {
|
||||||
_reconnectionPolicy.setShouldReconnect(false);
|
await _reconnectionPolicy.setShouldReconnect(false);
|
||||||
|
|
||||||
if (triggeredByUser) {
|
if (triggeredByUser) {
|
||||||
getPresenceManager().sendUnavailablePresence();
|
getPresenceManager().sendUnavailablePresence();
|
||||||
@ -1018,17 +1009,21 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Like [connect] but the Future resolves when the resource binding is either done or
|
/// Like [connect] but the Future resolves when the resource binding is either done or
|
||||||
/// SASL has failed.
|
/// SASL has failed.
|
||||||
Future<XmppConnectionResult> connectAwaitable({ String? lastResource }) async {
|
Future<XmppConnectionResult> connectAwaitable({ String? lastResource, bool waitForConnection = false }) async {
|
||||||
_runPreConnectionAssertions();
|
_runPreConnectionAssertions();
|
||||||
await _resetIsConnectionRunning();
|
await _resetIsConnectionRunning();
|
||||||
_connectionCompleter = Completer();
|
_connectionCompleter = Completer();
|
||||||
_log.finest('Calling connect() from connectAwaitable');
|
_log.finest('Calling connect() from connectAwaitable');
|
||||||
await connect(lastResource: lastResource);
|
await connect(
|
||||||
|
lastResource: lastResource,
|
||||||
|
waitForConnection: waitForConnection,
|
||||||
|
shouldReconnect: false,
|
||||||
|
);
|
||||||
return _connectionCompleter!.future;
|
return _connectionCompleter!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the connection process using the provided connection settings.
|
/// Start the connection process using the provided connection settings.
|
||||||
Future<void> connect({ String? lastResource }) async {
|
Future<void> connect({ String? lastResource, bool waitForConnection = false, bool shouldReconnect = true }) async {
|
||||||
if (_connectionState != XmppConnectionState.notConnected && _connectionState != XmppConnectionState.error) {
|
if (_connectionState != XmppConnectionState.notConnected && _connectionState != XmppConnectionState.error) {
|
||||||
_log.fine('Cancelling this connection attempt as one appears to be already running.');
|
_log.fine('Cancelling this connection attempt as one appears to be already running.');
|
||||||
return;
|
return;
|
||||||
@ -1036,15 +1031,25 @@ class XmppConnection {
|
|||||||
|
|
||||||
_runPreConnectionAssertions();
|
_runPreConnectionAssertions();
|
||||||
await _resetIsConnectionRunning();
|
await _resetIsConnectionRunning();
|
||||||
_reconnectionPolicy.setShouldReconnect(true);
|
|
||||||
|
|
||||||
if (lastResource != null) {
|
if (lastResource != null) {
|
||||||
setResource(lastResource);
|
setResource(lastResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldReconnect) {
|
||||||
|
await _reconnectionPolicy.setShouldReconnect(true);
|
||||||
|
}
|
||||||
|
|
||||||
await _reconnectionPolicy.reset();
|
await _reconnectionPolicy.reset();
|
||||||
await _sendEvent(ConnectingEvent());
|
await _sendEvent(ConnectingEvent());
|
||||||
|
|
||||||
|
// If requested, wait until we have a network connection
|
||||||
|
if (waitForConnection) {
|
||||||
|
_log.info('Waiting for okay from connectivityManager');
|
||||||
|
await _connectivityManager.waitForConnection();
|
||||||
|
_log.info('Got okay from connectivityManager');
|
||||||
|
}
|
||||||
|
|
||||||
final smManager = getStreamManagementManager();
|
final smManager = getStreamManagementManager();
|
||||||
String? host;
|
String? host;
|
||||||
int? port;
|
int? port;
|
||||||
|
18
packages/moxxmpp/lib/src/connectivity.dart
Normal file
18
packages/moxxmpp/lib/src/connectivity.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/// This manager class is responsible to tell the moxxmpp XmppConnection
|
||||||
|
/// when a connection can be established or not, regarding the network availability.
|
||||||
|
abstract class ConnectivityManager {
|
||||||
|
/// Returns true if a network connection is available. If not, returns false.
|
||||||
|
Future<bool> hasConnection();
|
||||||
|
|
||||||
|
/// Returns a future that resolves once we have a network connection.
|
||||||
|
Future<void> waitForConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An implementation of [ConnectivityManager] that is always connected.
|
||||||
|
class AlwaysConnectedConnectivityManager extends ConnectivityManager {
|
||||||
|
@override
|
||||||
|
Future<bool> hasConnection() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> waitForConnection() async {}
|
||||||
|
}
|
@ -1,14 +1,21 @@
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
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/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
|
|
||||||
abstract class XmppManagerBase {
|
abstract class XmppManagerBase {
|
||||||
late final XmppManagerAttributes _managerAttributes;
|
late final XmppManagerAttributes _managerAttributes;
|
||||||
late final Logger _log;
|
late final Logger _log;
|
||||||
|
|
||||||
|
/// Flag indicating that the post registration callback has been called once.
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
/// Registers the callbacks from XmppConnection with the manager
|
/// Registers the callbacks from XmppConnection with the manager
|
||||||
void register(XmppManagerAttributes attributes) {
|
void register(XmppManagerAttributes attributes) {
|
||||||
_managerAttributes = attributes;
|
_managerAttributes = attributes;
|
||||||
@ -49,6 +56,9 @@ abstract class XmppManagerBase {
|
|||||||
/// Return a list of features that should be included in a disco response.
|
/// Return a list of features that should be included in a disco response.
|
||||||
List<String> getDiscoFeatures() => [];
|
List<String> getDiscoFeatures() => [];
|
||||||
|
|
||||||
|
/// Return a list of identities that should be included in a disco response.
|
||||||
|
List<Identity> getDiscoIdentities() => [];
|
||||||
|
|
||||||
/// Return the Id (akin to xmlns) of this manager.
|
/// Return the Id (akin to xmlns) of this manager.
|
||||||
String getId();
|
String getId();
|
||||||
|
|
||||||
@ -64,6 +74,24 @@ abstract class XmppManagerBase {
|
|||||||
/// Returns true if the XEP is supported on the server. If not, returns false
|
/// Returns true if the XEP is supported on the server. If not, returns false
|
||||||
Future<bool> isSupported();
|
Future<bool> isSupported();
|
||||||
|
|
||||||
|
/// Called after the registration of all managers against the XmppConnection is done.
|
||||||
|
/// This method is only called once during the entire lifetime of it.
|
||||||
|
@mustCallSuper
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
final disco = getAttributes().getManagerById<DiscoManager>(discoManager);
|
||||||
|
if (disco != null) {
|
||||||
|
if (getDiscoFeatures().isNotEmpty) {
|
||||||
|
disco.addFeatures(getDiscoFeatures());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getDiscoIdentities().isNotEmpty) {
|
||||||
|
disco.addIdentities(getDiscoIdentities());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if
|
/// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if
|
||||||
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
|
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
|
||||||
Future<bool> runNonzaHandlers(XMLNode nonza) async {
|
Future<bool> runNonzaHandlers(XMLNode nonza) async {
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
const smManager = 'im.moxxmpp.streammangementmanager';
|
const smManager = 'org.moxxmpp.streammangementmanager';
|
||||||
const discoManager = 'im.moxxmpp.discomanager';
|
const discoManager = 'org.moxxmpp.discomanager';
|
||||||
const messageManager = 'im.moxxmpp.messagemanager';
|
const messageManager = 'org.moxxmpp.messagemanager';
|
||||||
const rosterManager = 'im.moxxmpp.rostermanager';
|
const rosterManager = 'org.moxxmpp.rostermanager';
|
||||||
const presenceManager = 'im.moxxmpp.presencemanager';
|
const presenceManager = 'org.moxxmpp.presencemanager';
|
||||||
const csiManager = 'im.moxxmpp.csimanager';
|
const csiManager = 'org.moxxmpp.csimanager';
|
||||||
const carbonsManager = 'im.moxxmpp.carbonsmanager';
|
const carbonsManager = 'org.moxxmpp.carbonsmanager';
|
||||||
const vcardManager = 'im.moxxmpp.vcardmanager';
|
const vcardManager = 'org.moxxmpp.vcardmanager';
|
||||||
const pubsubManager = 'im.moxxmpp.pubsubmanager';
|
const pubsubManager = 'org.moxxmpp.pubsubmanager';
|
||||||
const userAvatarManager = 'im.moxxmpp.useravatarmanager';
|
const userAvatarManager = 'org.moxxmpp.useravatarmanager';
|
||||||
const stableIdManager = 'im.moxxmpp.stableidmanager';
|
const stableIdManager = 'org.moxxmpp.stableidmanager';
|
||||||
const simsManager = 'im.moxxmpp.simsmanager';
|
const simsManager = 'org.moxxmpp.simsmanager';
|
||||||
const messageDeliveryReceiptManager = 'im.moxxmpp.messagedeliveryreceiptmanager';
|
const messageDeliveryReceiptManager = 'org.moxxmpp.messagedeliveryreceiptmanager';
|
||||||
const chatMarkerManager = 'im.moxxmpp.chatmarkermanager';
|
const chatMarkerManager = 'org.moxxmpp.chatmarkermanager';
|
||||||
const oobManager = 'im.moxxmpp.oobmanager';
|
const oobManager = 'org.moxxmpp.oobmanager';
|
||||||
const sfsManager = 'im.moxxmpp.sfsmanager';
|
const sfsManager = 'org.moxxmpp.sfsmanager';
|
||||||
const messageRepliesManager = 'im.moxxmpp.messagerepliesmanager';
|
const messageRepliesManager = 'org.moxxmpp.messagerepliesmanager';
|
||||||
const blockingManager = 'im.moxxmpp.blockingmanager';
|
const blockingManager = 'org.moxxmpp.blockingmanager';
|
||||||
const httpFileUploadManager = 'im.moxxmpp.httpfileuploadmanager';
|
const httpFileUploadManager = 'org.moxxmpp.httpfileuploadmanager';
|
||||||
const chatStateManager = 'im.moxxmpp.chatstatemanager';
|
const chatStateManager = 'org.moxxmpp.chatstatemanager';
|
||||||
const pingManager = 'im.moxxmpp.ping';
|
const pingManager = 'org.moxxmpp.ping';
|
||||||
const fileUploadNotificationManager = 'im.moxxmpp.fileuploadnotificationmanager';
|
const fileUploadNotificationManager = 'org.moxxmpp.fileuploadnotificationmanager';
|
||||||
const omemoManager = 'org.moxxmpp.omemomanager';
|
const omemoManager = 'org.moxxmpp.omemomanager';
|
||||||
const emeManager = 'org.moxxmpp.ememanager';
|
const emeManager = 'org.moxxmpp.ememanager';
|
||||||
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
|
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
|
||||||
@ -28,3 +28,4 @@ const messageRetractionManager = 'org.moxxmpp.messageretractionmanager';
|
|||||||
const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
|
const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
|
||||||
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
||||||
const stickersManager = 'org.moxxmpp.stickersmanager';
|
const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||||
|
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||||
|
@ -8,17 +8,19 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0115.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0414.dart';
|
|
||||||
|
|
||||||
|
/// A function that will be called when presence, outside of subscription request
|
||||||
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
|
/// presence.
|
||||||
|
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
||||||
|
|
||||||
|
/// A mandatory manager that handles initial presence sending, sending of subscription
|
||||||
|
/// request management requests and triggers events for incoming presence stanzas.
|
||||||
class PresenceManager extends XmppManagerBase {
|
class PresenceManager extends XmppManagerBase {
|
||||||
PresenceManager(this._capHashNode) : _capabilityHash = null, super();
|
PresenceManager() : super();
|
||||||
String? _capabilityHash;
|
|
||||||
final String _capHashNode;
|
|
||||||
|
|
||||||
String get capabilityHashNode => _capHashNode;
|
/// The list of pre-send callbacks.
|
||||||
|
final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getId() => presenceManager;
|
String getId() => presenceManager;
|
||||||
@ -40,6 +42,11 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
/// Register the pre-send callback [callback].
|
||||||
|
void registerPreSendCallback(PresencePreSendCallback callback) {
|
||||||
|
_presenceCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
|
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
switch (presence.type) {
|
switch (presence.type) {
|
||||||
@ -63,43 +70,26 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the capability hash.
|
|
||||||
Future<String> getCapabilityHash() async {
|
|
||||||
final manager = getAttributes().getManagerById(discoManager)! as DiscoManager;
|
|
||||||
_capabilityHash ??= await calculateCapabilityHash(
|
|
||||||
DiscoInfo(
|
|
||||||
manager.getRegisteredDiscoFeatures(),
|
|
||||||
manager.getIdentities(),
|
|
||||||
[],
|
|
||||||
getAttributes().getFullJID(),
|
|
||||||
),
|
|
||||||
getHashByName('sha-1')!,
|
|
||||||
);
|
|
||||||
|
|
||||||
return _capabilityHash!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends the initial presence to enable receiving messages.
|
/// Sends the initial presence to enable receiving messages.
|
||||||
Future<void> sendInitialPresence() async {
|
Future<void> sendInitialPresence() async {
|
||||||
|
final children = List<XMLNode>.from([
|
||||||
|
XMLNode(
|
||||||
|
tag: 'show',
|
||||||
|
text: 'chat',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (final callback in _presenceCallbacks) {
|
||||||
|
children.addAll(
|
||||||
|
await callback(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
attrs.sendNonza(
|
attrs.sendNonza(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
from: attrs.getFullJID().toString(),
|
from: attrs.getFullJID().toString(),
|
||||||
children: [
|
children: children,
|
||||||
XMLNode(
|
|
||||||
tag: 'show',
|
|
||||||
text: 'chat',
|
|
||||||
),
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'c',
|
|
||||||
xmlns: capsXmlns,
|
|
||||||
attributes: {
|
|
||||||
'hash': 'sha-1',
|
|
||||||
'node': _capHashNode,
|
|
||||||
'ver': await getCapabilityHash()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxxmpp/src/util/queue.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
/// A callback function to be called when the connection to the server has been lost.
|
/// A callback function to be called when the connection to the server has been lost.
|
||||||
@ -24,10 +25,16 @@ abstract class ReconnectionPolicy {
|
|||||||
bool _shouldAttemptReconnection = false;
|
bool _shouldAttemptReconnection = false;
|
||||||
|
|
||||||
/// Indicate if a reconnection attempt is currently running.
|
/// Indicate if a reconnection attempt is currently running.
|
||||||
bool _isReconnecting = false;
|
@protected
|
||||||
|
bool isReconnecting = false;
|
||||||
|
|
||||||
/// And the corresponding lock
|
/// And the corresponding lock
|
||||||
final Lock _isReconnectingLock = Lock();
|
@protected
|
||||||
|
final Lock lock = Lock();
|
||||||
|
|
||||||
|
/// The lock for accessing [_shouldAttemptReconnection]
|
||||||
|
@protected
|
||||||
|
final Lock shouldReconnectLock = Lock();
|
||||||
|
|
||||||
/// Called by XmppConnection to register the policy.
|
/// Called by XmppConnection to register the policy.
|
||||||
void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) {
|
void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) {
|
||||||
@ -48,96 +55,121 @@ abstract class ReconnectionPolicy {
|
|||||||
/// Caled by the XmppConnection when the reconnection was successful.
|
/// Caled by the XmppConnection when the reconnection was successful.
|
||||||
Future<void> onSuccess();
|
Future<void> onSuccess();
|
||||||
|
|
||||||
bool get shouldReconnect => _shouldAttemptReconnection;
|
Future<bool> getShouldReconnect() async {
|
||||||
|
return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether a reconnection attempt should be made.
|
/// Set whether a reconnection attempt should be made.
|
||||||
void setShouldReconnect(bool value) {
|
Future<void> setShouldReconnect(bool value) async {
|
||||||
_shouldAttemptReconnection = value;
|
return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the manager is currently triggering a reconnection. If not, returns
|
/// Returns true if the manager is currently triggering a reconnection. If not, returns
|
||||||
/// false.
|
/// false.
|
||||||
Future<bool> isReconnectionRunning() async {
|
Future<bool> isReconnectionRunning() async {
|
||||||
return _isReconnectingLock.synchronized(() => _isReconnecting);
|
return lock.synchronized(() => isReconnecting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the _isReconnecting state to [value].
|
/// Set the isReconnecting state to [value].
|
||||||
@protected
|
@protected
|
||||||
Future<void> setIsReconnecting(bool value) async {
|
Future<void> setIsReconnecting(bool value) async {
|
||||||
await _isReconnectingLock.synchronized(() async {
|
await lock.synchronized(() async {
|
||||||
_isReconnecting = value;
|
isReconnecting = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
Future<bool> testAndSetIsReconnecting() async {
|
|
||||||
return _isReconnectingLock.synchronized(() {
|
|
||||||
if (_isReconnecting) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
_isReconnecting = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
|
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
|
||||||
/// for every failed attempt.
|
/// for every failed attempt.
|
||||||
/// NOTE: This ReconnectionPolicy may be broken
|
/// NOTE: This ReconnectionPolicy may be broken
|
||||||
class ExponentialBackoffReconnectionPolicy extends ReconnectionPolicy {
|
class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||||
ExponentialBackoffReconnectionPolicy(this._maxBackoffTime) : super();
|
RandomBackoffReconnectionPolicy(
|
||||||
|
this._minBackoffTime,
|
||||||
|
this._maxBackoffTime,
|
||||||
|
) : assert(_minBackoffTime < _maxBackoffTime, '_minBackoffTime must be smaller than _maxBackoffTime'),
|
||||||
|
super();
|
||||||
|
|
||||||
/// The maximum time in seconds that a backoff step should be.
|
/// The maximum time in seconds that a backoff should be.
|
||||||
final int _maxBackoffTime;
|
final int _maxBackoffTime;
|
||||||
|
|
||||||
/// Amount of consecutive failed reconnections.
|
/// The minimum time in seconds that a backoff should be.
|
||||||
int _counter = 0;
|
final int _minBackoffTime;
|
||||||
|
|
||||||
/// Backoff timer.
|
/// Backoff timer.
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
|
|
||||||
|
final Lock _timerLock = Lock();
|
||||||
|
|
||||||
/// Logger.
|
/// Logger.
|
||||||
final Logger _log = Logger('ExponentialBackoffReconnectionPolicy');
|
final Logger _log = Logger('RandomBackoffReconnectionPolicy');
|
||||||
|
|
||||||
|
/// Event queue
|
||||||
|
final AsyncQueue _eventQueue = AsyncQueue();
|
||||||
|
|
||||||
/// Called when the backoff expired
|
/// Called when the backoff expired
|
||||||
Future<void> _onTimerElapsed() async {
|
Future<void> _onTimerElapsed() async {
|
||||||
final isReconnecting = await isReconnectionRunning();
|
_log.fine('Timer elapsed. Waiting for lock');
|
||||||
if (shouldReconnect) {
|
await lock.synchronized(() async {
|
||||||
if (!isReconnecting) {
|
_log.fine('Lock aquired');
|
||||||
await setIsReconnecting(true);
|
if (!(await getShouldReconnect())) {
|
||||||
await performReconnect!();
|
_log.fine('Backoff timer expired but getShouldReconnect() returned false');
|
||||||
} else {
|
return;
|
||||||
// Should never happen.
|
|
||||||
_log.fine('Backoff timer expired but reconnection is running, so doing nothing.');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (isReconnecting) {
|
||||||
|
_log.fine('Backoff timer expired but a reconnection is running, so doing nothing.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.fine('Triggering reconnect');
|
||||||
|
isReconnecting = true;
|
||||||
|
await performReconnect!();
|
||||||
|
});
|
||||||
|
|
||||||
|
await _timerLock.synchronized(() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _reset() async {
|
||||||
|
_log.finest('Resetting internal state');
|
||||||
|
|
||||||
|
await _timerLock.synchronized(() {
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
await setIsReconnecting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> reset() async {
|
Future<void> reset() async {
|
||||||
_log.finest('Resetting internal state');
|
// ignore: unnecessary_lambdas
|
||||||
_counter = 0;
|
await _eventQueue.addJob(() => _reset());
|
||||||
await setIsReconnecting(false);
|
}
|
||||||
|
|
||||||
if (_timer != null) {
|
Future<void> _onFailure() async {
|
||||||
_timer!.cancel();
|
final shouldContinue = await _timerLock.synchronized(() {
|
||||||
_timer = null;
|
return _timer == null;
|
||||||
|
});
|
||||||
|
if (!shouldContinue) {
|
||||||
|
_log.finest('_onFailure: Not backing off since _timer is already running');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final seconds = Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
|
||||||
|
_log.finest('Failure occured. Starting random backoff with ${seconds}s');
|
||||||
|
_timer?.cancel();
|
||||||
|
|
||||||
|
_timer = Timer(Duration(seconds: seconds), _onTimerElapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onFailure() async {
|
Future<void> onFailure() async {
|
||||||
_log.finest('Failure occured. Starting exponential backoff');
|
// ignore: unnecessary_lambdas
|
||||||
_counter++;
|
await _eventQueue.addJob(() => _onFailure());
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait at max 80 seconds.
|
|
||||||
final seconds = min(min(pow(2, _counter).toInt(), 80), _maxBackoffTime);
|
|
||||||
_timer = Timer(Duration(seconds: seconds), _onTimerElapsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
56
packages/moxxmpp/lib/src/util/queue.dart
Normal file
56
packages/moxxmpp/lib/src/util/queue.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
|
/// A job to be submitted to an [AsyncQueue].
|
||||||
|
typedef AsyncQueueJob = Future<void> Function();
|
||||||
|
|
||||||
|
/// A (hopefully) async-safe queue that attempts to force
|
||||||
|
/// in-order execution of its jobs.
|
||||||
|
class AsyncQueue {
|
||||||
|
/// The lock for accessing [AsyncQueue._lock] and [AsyncQueue._running].
|
||||||
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
|
/// The actual job queue.
|
||||||
|
final Queue<AsyncQueueJob> _queue = Queue<AsyncQueueJob>();
|
||||||
|
|
||||||
|
/// Indicates whether we are currently executing a job.
|
||||||
|
bool _running = false;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Queue<AsyncQueueJob> get queue => _queue;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
bool get isRunning => _running;
|
||||||
|
|
||||||
|
/// Adds a job [job] to the queue.
|
||||||
|
Future<void> addJob(AsyncQueueJob job) async {
|
||||||
|
await _lock.synchronized(() {
|
||||||
|
_queue.add(job);
|
||||||
|
|
||||||
|
if (!_running && _queue.isNotEmpty) {
|
||||||
|
_running = true;
|
||||||
|
unawaited(_popJob());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clear() async {
|
||||||
|
await _lock.synchronized(_queue.clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _popJob() async {
|
||||||
|
final job = _queue.removeFirst();
|
||||||
|
final future = job();
|
||||||
|
await future;
|
||||||
|
|
||||||
|
await _lock.synchronized(() {
|
||||||
|
if (_queue.isNotEmpty) {
|
||||||
|
unawaited(_popJob());
|
||||||
|
} else {
|
||||||
|
_running = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import 'package:moxxmpp/src/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
class DataFormOption {
|
class DataFormOption {
|
||||||
|
|
||||||
const DataFormOption({ required this.value, this.label });
|
const DataFormOption({ required this.value, this.label });
|
||||||
final String? label;
|
final String? label;
|
||||||
final String value;
|
final String value;
|
||||||
@ -23,7 +22,6 @@ class DataFormOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DataFormField {
|
class DataFormField {
|
||||||
|
|
||||||
const DataFormField({
|
const DataFormField({
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.values,
|
required this.values,
|
||||||
@ -60,7 +58,6 @@ class DataFormField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DataForm {
|
class DataForm {
|
||||||
|
|
||||||
const DataForm({
|
const DataForm({
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.instructions,
|
required this.instructions,
|
||||||
|
@ -6,20 +6,20 @@ import 'package:moxxmpp/src/stringxml.dart';
|
|||||||
|
|
||||||
Stanza buildDiscoInfoQueryStanza(String entity, String? node) {
|
Stanza buildDiscoInfoQueryStanza(String entity, String? node) {
|
||||||
return Stanza.iq(to: entity, type: 'get', children: [
|
return Stanza.iq(to: entity, type: 'get', children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: discoInfoXmlns,
|
xmlns: discoInfoXmlns,
|
||||||
attributes: node != null ? { 'node': node } : {},
|
attributes: node != null ? { 'node': node } : {},
|
||||||
)
|
)
|
||||||
],);
|
],);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stanza buildDiscoItemsQueryStanza(String entity, { String? node }) {
|
Stanza buildDiscoItemsQueryStanza(String entity, { String? node }) {
|
||||||
return Stanza.iq(to: entity, type: 'get', children: [
|
return Stanza.iq(to: entity, type: 'get', children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: discoItemsXmlns,
|
xmlns: discoItemsXmlns,
|
||||||
attributes: node != null ? { 'node': node } : {},
|
attributes: node != null ? { 'node': node } : {},
|
||||||
)
|
)
|
||||||
],);
|
],);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
|
|
||||||
class Identity {
|
class Identity {
|
||||||
|
|
||||||
const Identity({ required this.category, required this.type, this.name, this.lang });
|
const Identity({ required this.category, required this.type, this.name, this.lang });
|
||||||
final String category;
|
final String category;
|
||||||
final String type;
|
final String type;
|
||||||
@ -23,24 +24,96 @@ class Identity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
class DiscoInfo {
|
class DiscoInfo {
|
||||||
|
|
||||||
const DiscoInfo(
|
const DiscoInfo(
|
||||||
this.features,
|
this.features,
|
||||||
this.identities,
|
this.identities,
|
||||||
this.extendedInfo,
|
this.extendedInfo,
|
||||||
|
this.node,
|
||||||
this.jid,
|
this.jid,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory DiscoInfo.fromQuery(XMLNode query, JID jid) {
|
||||||
|
final features = List<String>.empty(growable: true);
|
||||||
|
final identities = List<Identity>.empty(growable: true);
|
||||||
|
final extendedInfo = List<DataForm>.empty(growable: true);
|
||||||
|
|
||||||
|
for (final element in query.children) {
|
||||||
|
if (element.tag == 'feature') {
|
||||||
|
features.add(element.attributes['var']! as String);
|
||||||
|
} else if (element.tag == 'identity') {
|
||||||
|
identities.add(
|
||||||
|
Identity(
|
||||||
|
category: element.attributes['category']! as String,
|
||||||
|
type: element.attributes['type']! as String,
|
||||||
|
name: element.attributes['name'] as String?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (element.tag == 'x' && element.attributes['xmlns'] == dataFormsXmlns) {
|
||||||
|
extendedInfo.add(
|
||||||
|
parseDataForm(element),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DiscoInfo(
|
||||||
|
features,
|
||||||
|
identities,
|
||||||
|
extendedInfo,
|
||||||
|
query.attributes['node'] as String?,
|
||||||
|
jid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final List<String> features;
|
final List<String> features;
|
||||||
final List<Identity> identities;
|
final List<Identity> identities;
|
||||||
final List<DataForm> extendedInfo;
|
final List<DataForm> extendedInfo;
|
||||||
final JID jid;
|
final String? node;
|
||||||
|
final JID? jid;
|
||||||
|
|
||||||
|
XMLNode toXml() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'query',
|
||||||
|
xmlns: discoInfoXmlns,
|
||||||
|
attributes: node != null ?
|
||||||
|
<String, String>{ 'node': node!, } :
|
||||||
|
<String, String>{},
|
||||||
|
children: [
|
||||||
|
...identities.map((identity) => identity.toXMLNode()),
|
||||||
|
...features.map((feature) => XMLNode(
|
||||||
|
tag: 'feature',
|
||||||
|
attributes: { 'var': feature, },
|
||||||
|
),),
|
||||||
|
|
||||||
|
if (extendedInfo.isNotEmpty)
|
||||||
|
...extendedInfo.map((ei) => ei.toXml()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
class DiscoItem {
|
class DiscoItem {
|
||||||
|
|
||||||
const DiscoItem({ required this.jid, this.node, this.name });
|
const DiscoItem({ required this.jid, this.node, this.name });
|
||||||
final String jid;
|
final String jid;
|
||||||
final String? node;
|
final String? node;
|
||||||
final String? name;
|
final String? name;
|
||||||
|
|
||||||
|
XMLNode toXml() {
|
||||||
|
final attributes = {
|
||||||
|
'jid': jid,
|
||||||
|
};
|
||||||
|
if (node != null) {
|
||||||
|
attributes['node'] = node!;
|
||||||
|
}
|
||||||
|
if (name != null) {
|
||||||
|
attributes['name'] = name!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return XMLNode(
|
||||||
|
tag: 'node',
|
||||||
|
attributes: attributes,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,21 @@ 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/presence.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/xeps/xep_0004.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/helpers.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0115.dart';
|
import 'package:moxxmpp/src/xeps/xep_0115.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
|
/// Callback that is called when a disco#info requests is received on a given node.
|
||||||
|
typedef DiscoInfoRequestCallback = Future<DiscoInfo> Function();
|
||||||
|
|
||||||
|
/// Callback that is called when a disco#items requests is received on a given node.
|
||||||
|
typedef DiscoItemsRequestCallback = Future<List<DiscoItem>> Function();
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class DiscoCacheKey {
|
class DiscoCacheKey {
|
||||||
const DiscoCacheKey(this.jid, this.node);
|
const DiscoCacheKey(this.jid, this.node);
|
||||||
@ -33,32 +37,48 @@ class DiscoCacheKey {
|
|||||||
int get hashCode => jid.hashCode ^ node.hashCode;
|
int get hashCode => jid.hashCode ^ node.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This manager implements XEP-0030 by providing a way of performing disco#info and
|
||||||
|
/// disco#items requests and answering those requests.
|
||||||
|
/// A caching mechanism is also provided.
|
||||||
class DiscoManager extends XmppManagerBase {
|
class DiscoManager extends XmppManagerBase {
|
||||||
DiscoManager()
|
/// [identities] is a list of disco identities that should be added by default
|
||||||
: _features = List.empty(growable: true),
|
/// to a disco#info response.
|
||||||
_capHashCache = {},
|
DiscoManager(List<Identity> identities)
|
||||||
_capHashInfoCache = {},
|
: _identities = List<Identity>.from(identities),
|
||||||
_discoInfoCache = {},
|
|
||||||
_runningInfoQueries = {},
|
|
||||||
_cacheLock = Lock(),
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
/// Our features
|
/// Our features
|
||||||
final List<String> _features;
|
final List<String> _features = List.empty(growable: true);
|
||||||
|
|
||||||
|
/// Disco identities that we advertise
|
||||||
|
final List<Identity> _identities;
|
||||||
|
|
||||||
/// Map full JID to Capability hashes
|
/// Map full JID to Capability hashes
|
||||||
final Map<String, CapabilityHashInfo> _capHashCache;
|
final Map<String, CapabilityHashInfo> _capHashCache = {};
|
||||||
|
|
||||||
/// Map capability hash to the disco info
|
/// Map capability hash to the disco info
|
||||||
final Map<String, DiscoInfo> _capHashInfoCache;
|
final Map<String, DiscoInfo> _capHashInfoCache = {};
|
||||||
|
|
||||||
/// Map full JID to Disco Info
|
/// Map full JID to Disco Info
|
||||||
final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache;
|
final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {};
|
||||||
|
|
||||||
/// Mapping the full JID to a list of running requests
|
/// Mapping the full JID to a list of running requests
|
||||||
final Map<DiscoCacheKey, List<Completer<Result<DiscoError, DiscoInfo>>>> _runningInfoQueries;
|
final Map<DiscoCacheKey, List<Completer<Result<DiscoError, DiscoInfo>>>> _runningInfoQueries = {};
|
||||||
|
|
||||||
/// Cache lock
|
/// Cache lock
|
||||||
final Lock _cacheLock;
|
final Lock _cacheLock = Lock();
|
||||||
|
|
||||||
|
/// disco#info callbacks: node -> Callback
|
||||||
|
final Map<String, DiscoInfoRequestCallback> _discoInfoCallbacks = {};
|
||||||
|
|
||||||
|
/// disco#items callbacks: node -> Callback
|
||||||
|
final Map<String, DiscoItemsRequestCallback> _discoItemsCallbacks = {};
|
||||||
|
|
||||||
|
/// The list of identities that are registered.
|
||||||
|
List<Identity> get identities => _identities;
|
||||||
|
|
||||||
|
/// The list of disco features that are registered.
|
||||||
|
List<String> get features => _features;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
bool hasInfoQueriesRunning() => _runningInfoQueries.isNotEmpty;
|
bool hasInfoQueriesRunning() => _runningInfoQueries.isNotEmpty;
|
||||||
@ -106,9 +126,19 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a callback [callback] for a disco#info query on [node].
|
||||||
|
void registerInfoCallback(String node, DiscoInfoRequestCallback callback) {
|
||||||
|
_discoInfoCallbacks[node] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a callback [callback] for a disco#items query on [node].
|
||||||
|
void registerItemsCallback(String node, DiscoItemsRequestCallback callback) {
|
||||||
|
_discoItemsCallbacks[node] = callback;
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a list of features to the possible disco info response.
|
/// Adds a list of features to the possible disco info response.
|
||||||
/// This function only adds features that are not already present in the disco features.
|
/// This function only adds features that are not already present in the disco features.
|
||||||
void addDiscoFeatures(List<String> features) {
|
void addFeatures(List<String> features) {
|
||||||
for (final feat in features) {
|
for (final feat in features) {
|
||||||
if (!_features.contains(feat)) {
|
if (!_features.contains(feat)) {
|
||||||
_features.add(feat);
|
_features.add(feat);
|
||||||
@ -116,6 +146,16 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a list of identities to the possible disco info response.
|
||||||
|
/// This function only adds features that are not already present in the disco features.
|
||||||
|
void addIdentities(List<Identity> identities) {
|
||||||
|
for (final identity in identities) {
|
||||||
|
if (!_identities.contains(identity)) {
|
||||||
|
_identities.add(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onPresence(JID from, Stanza presence) async {
|
Future<void> _onPresence(JID from, Stanza presence) async {
|
||||||
final c = presence.firstTag('c', xmlns: capsXmlns);
|
final c = presence.firstTag('c', xmlns: capsXmlns);
|
||||||
if (c == null) return;
|
if (c == null) return;
|
||||||
@ -146,45 +186,33 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the list of disco features registered.
|
/// Returns the [DiscoInfo] object that would be used as the response to a disco#info
|
||||||
List<String> getRegisteredDiscoFeatures() => _features;
|
/// query against our bare JID with no node. The results node attribute is set
|
||||||
|
/// to [node].
|
||||||
/// May be overriden. Specifies the identities which will be returned in a disco info response.
|
DiscoInfo getDiscoInfo(String? node) {
|
||||||
List<Identity> getIdentities() => const [ Identity(category: 'client', type: 'pc', name: 'moxxmpp', lang: 'en') ];
|
return DiscoInfo(
|
||||||
|
_features,
|
||||||
|
_identities,
|
||||||
|
const [],
|
||||||
|
node,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onDiscoInfoRequest(Stanza stanza, StanzaHandlerData state) async {
|
Future<StanzaHandlerData> _onDiscoInfoRequest(Stanza stanza, StanzaHandlerData state) async {
|
||||||
if (stanza.type != 'get') return state;
|
if (stanza.type != 'get') return state;
|
||||||
|
|
||||||
final presence = getAttributes().getManagerById(presenceManager)! as PresenceManager;
|
|
||||||
final query = stanza.firstTag('query', xmlns: discoInfoXmlns)!;
|
final query = stanza.firstTag('query', xmlns: discoInfoXmlns)!;
|
||||||
final node = query.attributes['node'] as String?;
|
final node = query.attributes['node'] as String?;
|
||||||
final capHash = await presence.getCapabilityHash();
|
|
||||||
final isCapabilityNode = node == '${presence.capabilityHashNode}#$capHash';
|
|
||||||
|
|
||||||
if (!isCapabilityNode && node != null) {
|
if (_discoInfoCallbacks.containsKey(node)) {
|
||||||
|
// We can now assume that node != null
|
||||||
|
final result = await _discoInfoCallbacks[node]!();
|
||||||
await reply(
|
await reply(
|
||||||
state,
|
state,
|
||||||
'error',
|
'result',
|
||||||
[
|
[
|
||||||
XMLNode.xmlns(
|
result.toXml(),
|
||||||
tag: 'query',
|
|
||||||
xmlns: discoInfoXmlns,
|
|
||||||
attributes: <String, String>{
|
|
||||||
'node': node
|
|
||||||
},
|
|
||||||
),
|
|
||||||
XMLNode(
|
|
||||||
tag: 'error',
|
|
||||||
attributes: <String, String>{
|
|
||||||
'type': 'cancel'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'not-allowed',
|
|
||||||
xmlns: fullStanzaXmlns,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -195,24 +223,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
state,
|
state,
|
||||||
'result',
|
'result',
|
||||||
[
|
[
|
||||||
XMLNode.xmlns(
|
getDiscoInfo(node).toXml(),
|
||||||
tag: 'query',
|
|
||||||
xmlns: discoInfoXmlns,
|
|
||||||
attributes: {
|
|
||||||
...!isCapabilityNode ? {} : {
|
|
||||||
'node': '${presence.capabilityHashNode}#$capHash'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
...getIdentities().map((identity) => identity.toXMLNode()),
|
|
||||||
..._features.map((feat) {
|
|
||||||
return XMLNode(
|
|
||||||
tag: 'feature',
|
|
||||||
attributes: <String, dynamic>{ 'var': feat },
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -223,30 +234,20 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
if (stanza.type != 'get') return state;
|
if (stanza.type != 'get') return state;
|
||||||
|
|
||||||
final query = stanza.firstTag('query', xmlns: discoItemsXmlns)!;
|
final query = stanza.firstTag('query', xmlns: discoItemsXmlns)!;
|
||||||
if (query.attributes['node'] != null) {
|
final node = query.attributes['node'] as String?;
|
||||||
// TODO(Unknown): Handle the node we specified for XEP-0115
|
if (_discoItemsCallbacks.containsKey(node)) {
|
||||||
|
final result = await _discoItemsCallbacks[node]!();
|
||||||
await reply(
|
await reply(
|
||||||
state,
|
state,
|
||||||
'error',
|
'result',
|
||||||
[
|
[
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: discoItemsXmlns,
|
xmlns: discoItemsXmlns,
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
'node': query.attributes['node']! as String,
|
'node': node!,
|
||||||
},
|
},
|
||||||
),
|
children: result.map((item) => item.toXml()).toList(),
|
||||||
XMLNode(
|
|
||||||
tag: 'error',
|
|
||||||
attributes: <String, dynamic>{
|
|
||||||
'type': 'cancel'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'not-allowed',
|
|
||||||
xmlns: fullStanzaXmlns,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -254,18 +255,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
return state.copyWith(done: true);
|
return state.copyWith(done: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await reply(
|
return state;
|
||||||
state,
|
|
||||||
'result',
|
|
||||||
[
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'query',
|
|
||||||
xmlns: discoItemsXmlns,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async {
|
Future<void> _exitDiscoInfoCriticalSection(DiscoCacheKey key, Result<DiscoError, DiscoInfo> result) async {
|
||||||
@ -322,34 +312,17 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
final error = stanza.firstTag('error');
|
if (stanza.attributes['type'] == 'error') {
|
||||||
if (error != null && stanza.attributes['type'] == 'error') {
|
//final error = stanza.firstTag('error');
|
||||||
final result = Result<DiscoError, DiscoInfo>(ErrorResponseDiscoError());
|
final result = Result<DiscoError, DiscoInfo>(ErrorResponseDiscoError());
|
||||||
await _exitDiscoInfoCriticalSection(cacheKey, result);
|
await _exitDiscoInfoCriticalSection(cacheKey, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
final features = List<String>.empty(growable: true);
|
|
||||||
final identities = List<Identity>.empty(growable: true);
|
|
||||||
|
|
||||||
for (final element in query.children) {
|
|
||||||
if (element.tag == 'feature') {
|
|
||||||
features.add(element.attributes['var']! as String);
|
|
||||||
} else if (element.tag == 'identity') {
|
|
||||||
identities.add(Identity(
|
|
||||||
category: element.attributes['category']! as String,
|
|
||||||
type: element.attributes['type']! as String,
|
|
||||||
name: element.attributes['name'] as String?,
|
|
||||||
),);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = Result<DiscoError, DiscoInfo>(
|
final result = Result<DiscoError, DiscoInfo>(
|
||||||
DiscoInfo(
|
DiscoInfo.fromQuery(
|
||||||
features,
|
query,
|
||||||
identities,
|
JID.fromString(entity),
|
||||||
query.findTags('x', xmlns: dataFormsXmlns).map(parseDataForm).toList(),
|
|
||||||
JID.fromString(stanza.attributes['from']! as String),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await _exitDiscoInfoCriticalSection(cacheKey, result);
|
await _exitDiscoInfoCriticalSection(cacheKey, result);
|
||||||
@ -367,8 +340,8 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
final query = stanza.firstTag('query');
|
final query = stanza.firstTag('query');
|
||||||
if (query == null) return Result(InvalidResponseDiscoError());
|
if (query == null) return Result(InvalidResponseDiscoError());
|
||||||
|
|
||||||
final error = stanza.firstTag('error');
|
if (stanza.type == 'error') {
|
||||||
if (error != null && stanza.type == 'error') {
|
//final error = stanza.firstTag('error');
|
||||||
//print("Disco Items error: " + error.toXml());
|
//print("Disco Items error: " + error.toXml());
|
||||||
return Result(ErrorResponseDiscoError());
|
return Result(ErrorResponseDiscoError());
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/presence.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0414.dart';
|
||||||
|
|
||||||
|
@immutable
|
||||||
class CapabilityHashInfo {
|
class CapabilityHashInfo {
|
||||||
|
|
||||||
const CapabilityHashInfo(this.ver, this.node, this.hash);
|
const CapabilityHashInfo(this.ver, this.node, this.hash);
|
||||||
final String ver;
|
final String ver;
|
||||||
final String node;
|
final String node;
|
||||||
@ -57,3 +65,86 @@ Future<String> calculateCapabilityHash(DiscoInfo info, HashAlgorithm algorithm)
|
|||||||
|
|
||||||
return base64.encode((await algorithm.hash(utf8.encode(buffer.toString()))).bytes);
|
return base64.encode((await algorithm.hash(utf8.encode(buffer.toString()))).bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A manager implementing the advertising of XEP-0115. It responds to the
|
||||||
|
/// disco#info requests on the specified node with the information provided by
|
||||||
|
/// the DiscoManager.
|
||||||
|
/// NOTE: This manager requires that the DiscoManager is also registered.
|
||||||
|
class EntityCapabilitiesManager extends XmppManagerBase {
|
||||||
|
EntityCapabilitiesManager(this._capabilityHashBase) : super();
|
||||||
|
|
||||||
|
/// The string that is both the node under which we advertise the disco info
|
||||||
|
/// and the base for the actual node on which we respond to disco#info requests.
|
||||||
|
final String _capabilityHashBase;
|
||||||
|
|
||||||
|
/// The cached capability hash.
|
||||||
|
String? _capabilityHash;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName() => 'EntityCapabilitiesManager';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getId() => entityCapabilitiesManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getDiscoFeatures() => [
|
||||||
|
capsXmlns,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Computes, if required, the capability hash of the data provided by
|
||||||
|
/// the DiscoManager.
|
||||||
|
Future<String> getCapabilityHash() async {
|
||||||
|
_capabilityHash ??= await calculateCapabilityHash(
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<DiscoManager>(discoManager)!
|
||||||
|
.getDiscoInfo(null),
|
||||||
|
getHashByName('sha-1')!,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _capabilityHash!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getNode() async {
|
||||||
|
final hash = await getCapabilityHash();
|
||||||
|
return '$_capabilityHashBase#$hash';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<DiscoInfo> _onInfoQuery() async {
|
||||||
|
return getAttributes()
|
||||||
|
.getManagerById<DiscoManager>(discoManager)!
|
||||||
|
.getDiscoInfo(await _getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<XMLNode>> _prePresenceSent() async {
|
||||||
|
return [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'c',
|
||||||
|
xmlns: capsXmlns,
|
||||||
|
attributes: {
|
||||||
|
'hash': 'sha-1',
|
||||||
|
'node': _capabilityHashBase,
|
||||||
|
'ver': await getCapabilityHash(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
getAttributes().getManagerById<DiscoManager>(discoManager)!.registerInfoCallback(
|
||||||
|
await _getNode(),
|
||||||
|
_onInfoQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<PresenceManager>(presenceManager)!
|
||||||
|
.registerPreSendCallback(
|
||||||
|
_prePresenceSent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,14 +9,14 @@ import 'package:moxxmpp/src/stanza.dart';
|
|||||||
/// Data summarizing the XEP-0461 data.
|
/// Data summarizing the XEP-0461 data.
|
||||||
class ReplyData {
|
class ReplyData {
|
||||||
const ReplyData({
|
const ReplyData({
|
||||||
required this.to,
|
|
||||||
required this.id,
|
required this.id,
|
||||||
|
this.to,
|
||||||
this.start,
|
this.start,
|
||||||
this.end,
|
this.end,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The bare JID to whom the reply applies to
|
/// The bare JID to whom the reply applies to
|
||||||
final String to;
|
final String? to;
|
||||||
|
|
||||||
/// The stanza ID of the message that is replied to
|
/// The stanza ID of the message that is replied to
|
||||||
final String id;
|
final String id;
|
||||||
@ -72,6 +72,11 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
@override
|
@override
|
||||||
String getId() => messageRepliesManager;
|
String getId() => messageRepliesManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getDiscoFeatures() => [
|
||||||
|
replyXmlns,
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
@ -90,7 +95,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
Future<StanzaHandlerData> _onMessage(Stanza stanza, StanzaHandlerData state) async {
|
Future<StanzaHandlerData> _onMessage(Stanza stanza, StanzaHandlerData state) async {
|
||||||
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
||||||
final id = reply.attributes['id']! as String;
|
final id = reply.attributes['id']! as String;
|
||||||
final to = reply.attributes['to']! as String;
|
final to = reply.attributes['to'] as String?;
|
||||||
int? start;
|
int? start;
|
||||||
int? end;
|
int? end;
|
||||||
|
|
||||||
@ -102,11 +107,13 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
end = int.parse(body.attributes['end']! as String);
|
end = int.parse(body.attributes['end']! as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.copyWith(reply: ReplyData(
|
return state.copyWith(
|
||||||
|
reply: ReplyData(
|
||||||
id: id,
|
id: id,
|
||||||
to: to,
|
to: to,
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
),);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
276
packages/moxxmpp/moxxmpp.doap
Normal file
276
packages/moxxmpp/moxxmpp.doap
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
||||||
|
xmlns='http://usefulinc.com/ns/doap#'
|
||||||
|
xmlns:foaf='http://xmlns.com/foaf/0.1/'
|
||||||
|
xmlns:xmpp='https://linkmauve.fr/ns/xmpp-doap#'>
|
||||||
|
<Project xml:lang='en'>
|
||||||
|
<name>moxxmpp</name>
|
||||||
|
<created>2021-12-26</created>
|
||||||
|
<homepage rdf:resource='https://codeberg.org/moxxy/moxxmpp'/>
|
||||||
|
<os>Linux</os>
|
||||||
|
<os>Windows</os>
|
||||||
|
<os>macOS</os>
|
||||||
|
<os>Android</os>
|
||||||
|
<os>iOS</os>
|
||||||
|
|
||||||
|
<programming-language>Dart</programming-language>
|
||||||
|
|
||||||
|
<maintainer>
|
||||||
|
<foaf:Person>
|
||||||
|
<foaf:name>Alexander "Polynomdivision"</foaf:name>
|
||||||
|
<foaf:homepage rdf:resource="https://blog.polynom.me" />
|
||||||
|
</foaf:Person>
|
||||||
|
</maintainer>
|
||||||
|
|
||||||
|
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html" />
|
||||||
|
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>2.13.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0030.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>2.5rc3</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.2</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0060.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.24.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0066.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Only jabber:x:oob</xmpp:note>
|
||||||
|
<xmpp:version>1.5</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Receiving data</xmpp:note>
|
||||||
|
<xmpp:version>1.1.4</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0085.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>2.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0115.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.5.2</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.4.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.3.0</xmpp:version>
|
||||||
|
<xmpp:note xml:lang="en">Not plugged into the UI</xmpp:note>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0198.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.6</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.0.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0297.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Exists only as part of support for XEP-0280</xmpp:note>
|
||||||
|
<xmpp:version>1.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0300.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Supports only Sha256, Sha512 and blake2b512</xmpp:note>
|
||||||
|
<xmpp:version>1.0.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.2.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Read-only support</xmpp:note>
|
||||||
|
<xmpp:version>0.4</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:note xml:lang="en">Write-only support</xmpp:note>
|
||||||
|
<xmpp:version>0.3.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>1.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0359.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.6.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.1.0</xmpp:version>
|
||||||
|
<xmpp:note xml:lang="en">Only handles the success case; not accessible via the App</xmpp:note>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>1.1.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.4.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0384.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.8.3</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0420.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>0.4.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0424.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.3.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0444.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.1.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0446.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.2.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0447.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.1.2</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0448.html" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>0.2.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0449.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.1.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0461.html" />
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.2.0</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-extensible-file-thumbnails.md" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>0.2.1</xmpp:version>
|
||||||
|
<xmpp:note xml:lang="en">Only Blurhash is implemented</xmpp:note>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md" />
|
||||||
|
<xmpp:status>partial</xmpp:status>
|
||||||
|
<xmpp:version>0.0.5</xmpp:version>
|
||||||
|
<xmpp:note xml:lang="en">Sending and receiving implemented; cancellation not implemented</xmpp:note>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
|
</Project>
|
||||||
|
</rdf:RDF>
|
43
packages/moxxmpp/test/async_queue_test.dart
Normal file
43
packages/moxxmpp/test/async_queue_test.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:moxxmpp/src/util/queue.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Test the async queue', () async {
|
||||||
|
final queue = AsyncQueue();
|
||||||
|
int future1Finish = 0;
|
||||||
|
int future2Finish = 0;
|
||||||
|
int future3Finish = 0;
|
||||||
|
|
||||||
|
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future1Finish = DateTime.now().millisecondsSinceEpoch));
|
||||||
|
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future2Finish = DateTime.now().millisecondsSinceEpoch));
|
||||||
|
await queue.addJob(() => Future<void>.delayed(const Duration(seconds: 3), () => future3Finish = DateTime.now().millisecondsSinceEpoch));
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 12));
|
||||||
|
|
||||||
|
// The three futures must be done
|
||||||
|
expect(future1Finish != 0, true);
|
||||||
|
expect(future2Finish != 0, true);
|
||||||
|
expect(future3Finish != 0, true);
|
||||||
|
|
||||||
|
// The end times of the futures must be ordered (on a timeline)
|
||||||
|
// |-- future1Finish -- future2Finish -- future3Finish --|
|
||||||
|
expect(
|
||||||
|
future1Finish < future2Finish && future1Finish < future3Finish,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
future2Finish < future3Finish && future2Finish > future1Finish,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
future3Finish > future1Finish && future3Finish > future2Finish,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The queue must be empty at the end
|
||||||
|
expect(queue.queue.isEmpty, true);
|
||||||
|
|
||||||
|
// The queue must not be executing anything at the end
|
||||||
|
expect(queue.isRunning, false);
|
||||||
|
});
|
||||||
|
}
|
@ -54,16 +54,20 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
final connection = XmppConnection(TestingReconnectionPolicy(), stubSocket)
|
final connection = XmppConnection(
|
||||||
..registerFeatureNegotiators([
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
stubSocket,
|
||||||
|
)..registerFeatureNegotiators([
|
||||||
StubNegotiator1(),
|
StubNegotiator1(),
|
||||||
StubNegotiator2(),
|
StubNegotiator2(),
|
||||||
])
|
])
|
||||||
..registerManagers([
|
..registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
])
|
])
|
||||||
..setConnectionSettings(
|
..setConnectionSettings(
|
||||||
ConnectionSettings(
|
ConnectionSettings(
|
||||||
|
@ -3,14 +3,14 @@ import 'package:test/test.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Parsing', () {
|
test('Parsing', () {
|
||||||
const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
const testData = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
||||||
|
|
||||||
final form = parseDataForm(XMLNode.fromString(testData));
|
final form = parseDataForm(XMLNode.fromString(testData));
|
||||||
expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo');
|
expect(form.getFieldByVar('FORM_TYPE')?.values.first, 'urn:xmpp:dataforms:softwareinfo');
|
||||||
expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]);
|
expect(form.getFieldByVar('ip_version')?.values, [ 'ipv4', 'ipv6' ]);
|
||||||
expect(form.getFieldByVar('os')?.values.first, 'Mac');
|
expect(form.getFieldByVar('os')?.values.first, 'Mac');
|
||||||
expect(form.getFieldByVar('os_version')?.values.first, '10.5.1');
|
expect(form.getFieldByVar('os_version')?.values.first, '10.5.1');
|
||||||
expect(form.getFieldByVar('software')?.values.first, 'Psi');
|
expect(form.getFieldByVar('software')?.values.first, 'Psi');
|
||||||
expect(form.getFieldByVar('software_version')?.values.first, '0.11');
|
expect(form.getFieldByVar('software_version')?.values.first, '0.11');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ void main() {
|
|||||||
ignoreId: true,
|
ignoreId: true,
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>",
|
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>",
|
||||||
'',
|
'',
|
||||||
),
|
),
|
||||||
StanzaExpectation(
|
StanzaExpectation(
|
||||||
@ -65,7 +65,11 @@ void main() {
|
|||||||
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -73,10 +77,11 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(),
|
RosterManager(TestingRosterStateManager(null, [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators(
|
conn.registerFeatureNegotiators(
|
||||||
[
|
[
|
@ -4,164 +4,167 @@ import 'package:test/test.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Test XEP example', () async {
|
test('Test XEP example', () async {
|
||||||
final data = DiscoInfo(
|
final data = DiscoInfo(
|
||||||
[
|
[
|
||||||
'http://jabber.org/protocol/caps',
|
'http://jabber.org/protocol/caps',
|
||||||
'http://jabber.org/protocol/disco#info',
|
'http://jabber.org/protocol/disco#info',
|
||||||
'http://jabber.org/protocol/disco#items',
|
'http://jabber.org/protocol/disco#items',
|
||||||
'http://jabber.org/protocol/muc'
|
'http://jabber.org/protocol/muc'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Identity(
|
Identity(
|
||||||
category: 'client',
|
category: 'client',
|
||||||
type: 'pc',
|
type: 'pc',
|
||||||
name: 'Exodus 0.9.1',
|
name: 'Exodus 0.9.1',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
JID.fromString('some@user.local/test'),
|
null,
|
||||||
);
|
JID.fromString('some@user.local/test'),
|
||||||
|
);
|
||||||
|
|
||||||
final hash = await calculateCapabilityHash(data, Sha1());
|
final hash = await calculateCapabilityHash(data, Sha1());
|
||||||
expect(hash, 'QgayPKawpkPSDYmwT/WM94uAlu0=');
|
expect(hash, 'QgayPKawpkPSDYmwT/WM94uAlu0=');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test complex generation example', () async {
|
test('Test complex generation example', () async {
|
||||||
const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
const extDiscoDataString = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>urn:xmpp:dataforms:softwareinfo</value></field><field var='ip_version' type='text-multi' ><value>ipv4</value><value>ipv6</value></field><field var='os'><value>Mac</value></field><field var='os_version'><value>10.5.1</value></field><field var='software'><value>Psi</value></field><field var='software_version'><value>0.11</value></field></x>";
|
||||||
final data = DiscoInfo(
|
final data = DiscoInfo(
|
||||||
[
|
[
|
||||||
'http://jabber.org/protocol/caps',
|
'http://jabber.org/protocol/caps',
|
||||||
'http://jabber.org/protocol/disco#info',
|
'http://jabber.org/protocol/disco#info',
|
||||||
'http://jabber.org/protocol/disco#items',
|
'http://jabber.org/protocol/disco#items',
|
||||||
'http://jabber.org/protocol/muc'
|
'http://jabber.org/protocol/muc'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
const Identity(
|
const Identity(
|
||||||
category: 'client',
|
category: 'client',
|
||||||
type: 'pc',
|
type: 'pc',
|
||||||
name: 'Psi 0.11',
|
name: 'Psi 0.11',
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
),
|
),
|
||||||
const Identity(
|
const Identity(
|
||||||
category: 'client',
|
category: 'client',
|
||||||
type: 'pc',
|
type: 'pc',
|
||||||
name: 'Ψ 0.11',
|
name: 'Ψ 0.11',
|
||||||
lang: 'el',
|
lang: 'el',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[ parseDataForm(XMLNode.fromString(extDiscoDataString)) ],
|
[ parseDataForm(XMLNode.fromString(extDiscoDataString)) ],
|
||||||
JID.fromString('some@user.local/test'),
|
null,
|
||||||
);
|
JID.fromString('some@user.local/test'),
|
||||||
|
);
|
||||||
|
|
||||||
final hash = await calculateCapabilityHash(data, Sha1());
|
final hash = await calculateCapabilityHash(data, Sha1());
|
||||||
expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w=');
|
expect(hash, 'q07IKJEyjvHSyhy//CH0CxmKi8w=');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test Gajim capability hash computation', () async {
|
test('Test Gajim capability hash computation', () async {
|
||||||
// TODO: This one fails
|
// TODO: This one fails
|
||||||
/*
|
/*
|
||||||
final data = DiscoInfo(
|
final data = DiscoInfo(
|
||||||
features: [
|
features: [
|
||||||
"http://jabber.org/protocol/bytestreams",
|
"http://jabber.org/protocol/bytestreams",
|
||||||
"http://jabber.org/protocol/muc",
|
"http://jabber.org/protocol/muc",
|
||||||
"http://jabber.org/protocol/commands",
|
"http://jabber.org/protocol/commands",
|
||||||
"http://jabber.org/protocol/disco#info",
|
"http://jabber.org/protocol/disco#info",
|
||||||
"jabber:iq:last",
|
"jabber:iq:last",
|
||||||
"jabber:x:data",
|
"jabber:x:data",
|
||||||
"jabber:x:encrypted",
|
"jabber:x:encrypted",
|
||||||
"urn:xmpp:ping",
|
"urn:xmpp:ping",
|
||||||
"http://jabber.org/protocol/chatstates",
|
"http://jabber.org/protocol/chatstates",
|
||||||
"urn:xmpp:receipts",
|
"urn:xmpp:receipts",
|
||||||
"urn:xmpp:time",
|
"urn:xmpp:time",
|
||||||
"jabber:iq:version",
|
"jabber:iq:version",
|
||||||
"http://jabber.org/protocol/rosterx",
|
"http://jabber.org/protocol/rosterx",
|
||||||
"urn:xmpp:sec-label:0",
|
"urn:xmpp:sec-label:0",
|
||||||
"jabber:x:conference",
|
"jabber:x:conference",
|
||||||
"urn:xmpp:message-correct:0",
|
"urn:xmpp:message-correct:0",
|
||||||
"urn:xmpp:chat-markers:0",
|
"urn:xmpp:chat-markers:0",
|
||||||
"urn:xmpp:eme:0",
|
"urn:xmpp:eme:0",
|
||||||
"http://jabber.org/protocol/xhtml-im",
|
"http://jabber.org/protocol/xhtml-im",
|
||||||
"urn:xmpp:hashes:2",
|
"urn:xmpp:hashes:2",
|
||||||
"urn:xmpp:hash-function-text-names:md5",
|
"urn:xmpp:hash-function-text-names:md5",
|
||||||
"urn:xmpp:hash-function-text-names:sha-1",
|
"urn:xmpp:hash-function-text-names:sha-1",
|
||||||
"urn:xmpp:hash-function-text-names:sha-256",
|
"urn:xmpp:hash-function-text-names:sha-256",
|
||||||
"urn:xmpp:hash-function-text-names:sha-512",
|
"urn:xmpp:hash-function-text-names:sha-512",
|
||||||
"urn:xmpp:hash-function-text-names:sha3-256",
|
"urn:xmpp:hash-function-text-names:sha3-256",
|
||||||
"urn:xmpp:hash-function-text-names:sha3-512",
|
"urn:xmpp:hash-function-text-names:sha3-512",
|
||||||
"urn:xmpp:hash-function-text-names:id-blake2b256",
|
"urn:xmpp:hash-function-text-names:id-blake2b256",
|
||||||
"urn:xmpp:hash-function-text-names:id-blake2b512",
|
"urn:xmpp:hash-function-text-names:id-blake2b512",
|
||||||
"urn:xmpp:jingle:1",
|
"urn:xmpp:jingle:1",
|
||||||
"urn:xmpp:jingle:apps:file-transfer:5",
|
"urn:xmpp:jingle:apps:file-transfer:5",
|
||||||
"urn:xmpp:jingle:security:xtls:0",
|
"urn:xmpp:jingle:security:xtls:0",
|
||||||
"urn:xmpp:jingle:transports:s5b:1",
|
"urn:xmpp:jingle:transports:s5b:1",
|
||||||
"urn:xmpp:jingle:transports:ibb:1",
|
"urn:xmpp:jingle:transports:ibb:1",
|
||||||
"urn:xmpp:avatar:metadata+notify",
|
"urn:xmpp:avatar:metadata+notify",
|
||||||
"urn:xmpp:message-moderate:0",
|
"urn:xmpp:message-moderate:0",
|
||||||
"http://jabber.org/protocol/tune+notify",
|
"http://jabber.org/protocol/tune+notify",
|
||||||
"http://jabber.org/protocol/geoloc+notify",
|
"http://jabber.org/protocol/geoloc+notify",
|
||||||
"http://jabber.org/protocol/nick+notify",
|
"http://jabber.org/protocol/nick+notify",
|
||||||
"eu.siacs.conversations.axolotl.devicelist+notify",
|
"eu.siacs.conversations.axolotl.devicelist+notify",
|
||||||
],
|
],
|
||||||
identities: [
|
identities: [
|
||||||
Identity(
|
Identity(
|
||||||
category: "client",
|
category: "client",
|
||||||
type: "pc",
|
type: "pc",
|
||||||
name: "Gajim"
|
name: "Gajim"
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final hash = await calculateCapabilityHash(data, Sha1());
|
final hash = await calculateCapabilityHash(data, Sha1());
|
||||||
expect(hash, "T7fOZrtBnV8sDA2fFTS59vyOyUs=");
|
expect(hash, "T7fOZrtBnV8sDA2fFTS59vyOyUs=");
|
||||||
*/
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test Conversations hash computation', () async {
|
test('Test Conversations hash computation', () async {
|
||||||
final data = DiscoInfo(
|
final data = DiscoInfo(
|
||||||
[
|
[
|
||||||
'eu.siacs.conversations.axolotl.devicelist+notify',
|
'eu.siacs.conversations.axolotl.devicelist+notify',
|
||||||
'http://jabber.org/protocol/caps',
|
'http://jabber.org/protocol/caps',
|
||||||
'http://jabber.org/protocol/chatstates',
|
'http://jabber.org/protocol/chatstates',
|
||||||
'http://jabber.org/protocol/disco#info',
|
'http://jabber.org/protocol/disco#info',
|
||||||
'http://jabber.org/protocol/muc',
|
'http://jabber.org/protocol/muc',
|
||||||
'http://jabber.org/protocol/nick+notify',
|
'http://jabber.org/protocol/nick+notify',
|
||||||
'jabber:iq:version',
|
'jabber:iq:version',
|
||||||
'jabber:x:conference',
|
'jabber:x:conference',
|
||||||
'jabber:x:oob',
|
'jabber:x:oob',
|
||||||
'storage:bookmarks+notify',
|
'storage:bookmarks+notify',
|
||||||
'urn:xmpp:avatar:metadata+notify',
|
'urn:xmpp:avatar:metadata+notify',
|
||||||
'urn:xmpp:chat-markers:0',
|
'urn:xmpp:chat-markers:0',
|
||||||
'urn:xmpp:jingle-message:0',
|
'urn:xmpp:jingle-message:0',
|
||||||
'urn:xmpp:jingle:1',
|
'urn:xmpp:jingle:1',
|
||||||
'urn:xmpp:jingle:apps:dtls:0',
|
'urn:xmpp:jingle:apps:dtls:0',
|
||||||
'urn:xmpp:jingle:apps:file-transfer:3',
|
'urn:xmpp:jingle:apps:file-transfer:3',
|
||||||
'urn:xmpp:jingle:apps:file-transfer:4',
|
'urn:xmpp:jingle:apps:file-transfer:4',
|
||||||
'urn:xmpp:jingle:apps:file-transfer:5',
|
'urn:xmpp:jingle:apps:file-transfer:5',
|
||||||
'urn:xmpp:jingle:apps:rtp:1',
|
'urn:xmpp:jingle:apps:rtp:1',
|
||||||
'urn:xmpp:jingle:apps:rtp:audio',
|
'urn:xmpp:jingle:apps:rtp:audio',
|
||||||
'urn:xmpp:jingle:apps:rtp:video',
|
'urn:xmpp:jingle:apps:rtp:video',
|
||||||
'urn:xmpp:jingle:jet-omemo:0',
|
'urn:xmpp:jingle:jet-omemo:0',
|
||||||
'urn:xmpp:jingle:jet:0',
|
'urn:xmpp:jingle:jet:0',
|
||||||
'urn:xmpp:jingle:transports:ibb:1',
|
'urn:xmpp:jingle:transports:ibb:1',
|
||||||
'urn:xmpp:jingle:transports:ice-udp:1',
|
'urn:xmpp:jingle:transports:ice-udp:1',
|
||||||
'urn:xmpp:jingle:transports:s5b:1',
|
'urn:xmpp:jingle:transports:s5b:1',
|
||||||
'urn:xmpp:message-correct:0',
|
'urn:xmpp:message-correct:0',
|
||||||
'urn:xmpp:ping',
|
'urn:xmpp:ping',
|
||||||
'urn:xmpp:receipts',
|
'urn:xmpp:receipts',
|
||||||
'urn:xmpp:time'
|
'urn:xmpp:time'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
Identity(
|
Identity(
|
||||||
category: 'client',
|
category: 'client',
|
||||||
type: 'phone',
|
type: 'phone',
|
||||||
name: 'Conversations',
|
name: 'Conversations',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
JID.fromString('user@server.local/test'),
|
null,
|
||||||
);
|
JID.fromString('user@server.local/test'),
|
||||||
|
);
|
||||||
|
|
||||||
final hash = await calculateCapabilityHash(data, Sha1());
|
final hash = await calculateCapabilityHash(data, Sha1());
|
||||||
expect(hash, 'zcIke+Rk13ah4d1pwDG7bEZsVwA=');
|
expect(hash, 'zcIke+Rk13ah4d1pwDG7bEZsVwA=');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('hallo@example.server/uwu'),
|
getFullJID: () => JID.fromString('hallo@example.server/uwu'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -233,7 +233,11 @@ void main() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -242,12 +246,13 @@ void main() {
|
|||||||
),);
|
),);
|
||||||
final sm = StreamManagementManager();
|
final sm = StreamManagementManager();
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
sm,
|
sm,
|
||||||
CarbonsManager()..forceEnable(),
|
CarbonsManager()..forceEnable(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators(
|
conn.registerFeatureNegotiators(
|
||||||
[
|
[
|
||||||
@ -343,7 +348,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" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='QRTBC5cg/oYd+UOTYazSQR4zb/I=' /></presence>",
|
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>",
|
||||||
'<iq type="result" />',
|
'<iq type="result" />',
|
||||||
),
|
),
|
||||||
StanzaExpectation(
|
StanzaExpectation(
|
||||||
@ -355,7 +360,11 @@ void main() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -364,12 +373,13 @@ void main() {
|
|||||||
),);
|
),);
|
||||||
final sm = StreamManagementManager();
|
final sm = StreamManagementManager();
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
sm,
|
sm,
|
||||||
CarbonsManager()..forceEnable(),
|
CarbonsManager()..forceEnable(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators(
|
conn.registerFeatureNegotiators(
|
||||||
[
|
[
|
||||||
@ -510,7 +520,11 @@ void main() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -518,9 +532,9 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
@ -602,7 +616,11 @@ void main() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -610,9 +628,9 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
@ -694,7 +712,11 @@ void main() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -702,9 +724,9 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
]);
|
]);
|
||||||
|
@ -22,7 +22,7 @@ void main() {
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
|
getFullJID: () => JID.fromString('bob@xmpp.example/uwu'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
);
|
);
|
||||||
final manager = CarbonsManager();
|
final manager = CarbonsManager();
|
||||||
|
@ -50,7 +50,7 @@ void main() {
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ void main() {
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,52 +3,52 @@ import 'package:test/test.dart';
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Test the XEP-0363 header preparation', () {
|
group('Test the XEP-0363 header preparation', () {
|
||||||
test('invariance', () {
|
test('invariance', () {
|
||||||
final headers = {
|
final headers = {
|
||||||
'authorization': 'Basic Base64String==',
|
'authorization': 'Basic Base64String==',
|
||||||
'cookie': 'foo=bar; user=romeo'
|
'cookie': 'foo=bar; user=romeo'
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
prepareHeaders(headers),
|
prepareHeaders(headers),
|
||||||
headers,
|
headers,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('invariance through uppercase', () {
|
test('invariance through uppercase', () {
|
||||||
final headers = {
|
final headers = {
|
||||||
'Authorization': 'Basic Base64String==',
|
'Authorization': 'Basic Base64String==',
|
||||||
'Cookie': 'foo=bar; user=romeo'
|
'Cookie': 'foo=bar; user=romeo'
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
prepareHeaders(headers),
|
prepareHeaders(headers),
|
||||||
headers,
|
headers,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('remove unspecified headers', () {
|
test('remove unspecified headers', () {
|
||||||
final headers = {
|
final headers = {
|
||||||
'Authorization': 'Basic Base64String==',
|
'Authorization': 'Basic Base64String==',
|
||||||
'Cookie': 'foo=bar; user=romeo',
|
'Cookie': 'foo=bar; user=romeo',
|
||||||
'X-Tracking': 'Base64String=='
|
'X-Tracking': 'Base64String=='
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
prepareHeaders(headers),
|
prepareHeaders(headers),
|
||||||
{
|
{
|
||||||
'Authorization': 'Basic Base64String==',
|
'Authorization': 'Basic Base64String==',
|
||||||
'Cookie': 'foo=bar; user=romeo',
|
'Cookie': 'foo=bar; user=romeo',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('remove newlines', () {
|
test('remove newlines', () {
|
||||||
final headers = {
|
final headers = {
|
||||||
'Authorization': '\n\nBasic Base64String==\n\n',
|
'Authorization': '\n\nBasic Base64String==\n\n',
|
||||||
'\nCookie\r\n': 'foo=bar; user=romeo',
|
'\nCookie\r\n': 'foo=bar; user=romeo',
|
||||||
};
|
};
|
||||||
expect(
|
expect(
|
||||||
prepareHeaders(headers),
|
prepareHeaders(headers),
|
||||||
{
|
{
|
||||||
'Authorization': 'Basic Base64String==',
|
'Authorization': 'Basic Base64String==',
|
||||||
'Cookie': 'foo=bar; user=romeo',
|
'Cookie': 'foo=bar; user=romeo',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ Future<bool> testRosterManager(String bareJid, String resource, String stanzaStr
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('$bareJid/$resource'),
|
getFullJID: () => JID.fromString('$bareJid/$resource'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
),);
|
),);
|
||||||
|
|
||||||
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
|
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
|
||||||
@ -118,7 +118,10 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
// TODO: This test is broken since we query the server and enable carbons
|
// TODO: This test is broken since we query the server and enable carbons
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -126,11 +129,12 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators(
|
conn.registerFeatureNegotiators(
|
||||||
[
|
[
|
||||||
@ -172,7 +176,11 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
var receivedEvent = false;
|
var receivedEvent = false;
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -180,10 +188,11 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator()
|
SaslPlainNegotiator()
|
||||||
@ -226,7 +235,11 @@ void main() {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
var receivedEvent = false;
|
var receivedEvent = false;
|
||||||
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), fakeSocket);
|
final XmppConnection conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
fakeSocket,
|
||||||
|
);
|
||||||
conn.setConnectionSettings(ConnectionSettings(
|
conn.setConnectionSettings(ConnectionSettings(
|
||||||
jid: JID.fromString('polynomdivision@test.server'),
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
password: 'aaaa',
|
password: 'aaaa',
|
||||||
@ -234,10 +247,11 @@ void main() {
|
|||||||
allowPlainAuth: true,
|
allowPlainAuth: true,
|
||||||
),);
|
),);
|
||||||
conn.registerManagers([
|
conn.registerManagers([
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
PingManager(),
|
PingManager(),
|
||||||
|
EntityCapabilitiesManager('http://moxxmpp.example'),
|
||||||
]);
|
]);
|
||||||
conn.registerFeatureNegotiators([
|
conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator()
|
SaslPlainNegotiator()
|
||||||
@ -326,7 +340,7 @@ void main() {
|
|||||||
isFeatureSupported: (_) => false,
|
isFeatureSupported: (_) => false,
|
||||||
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
|
||||||
getSocket: () => StubTCPSocket(play: []),
|
getSocket: () => StubTCPSocket(play: []),
|
||||||
getConnection: () => XmppConnection(TestingReconnectionPolicy(), StubTCPSocket(play: [])),
|
getConnection: () => XmppConnection(TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(), StubTCPSocket(play: [])),
|
||||||
),);
|
),);
|
||||||
|
|
||||||
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
|
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
|
||||||
|
Loading…
Reference in New Issue
Block a user