Merge branch 'master' into xep_0045
This commit is contained in:
commit
9e70e802ef
@ -30,24 +30,26 @@ class EchoMessageManager extends XmppManagerBase {
|
|||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final body = stanza.firstTag('body');
|
final body = stanza.firstTag('body');
|
||||||
if (body == null) return state.copyWith(done: true);
|
if (body == null) return state..done = true;
|
||||||
|
|
||||||
final bodyText = body.innerText();
|
final bodyText = body.innerText();
|
||||||
|
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
Stanza.message(
|
StanzaDetails(
|
||||||
to: stanza.from,
|
Stanza.message(
|
||||||
children: [
|
to: stanza.from,
|
||||||
XMLNode(
|
children: [
|
||||||
tag: 'body',
|
XMLNode(
|
||||||
text: 'Hello, ${stanza.from}! You said "$bodyText"',
|
tag: 'body',
|
||||||
),
|
text: 'Hello, ${stanza.from}! You said "$bodyText"',
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
),
|
),
|
||||||
awaitable: false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
return state..done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
116
examples_dart/bin/omemo_client.dart
Normal file
116
examples_dart/bin/omemo_client.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import 'package:chalkdart/chalk.dart';
|
||||||
|
import 'package:cli_repl/cli_repl.dart';
|
||||||
|
import 'package:example_dart/arguments.dart';
|
||||||
|
import 'package:example_dart/socket.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:omemo_dart/omemo_dart.dart' as omemo;
|
||||||
|
|
||||||
|
void main(List<String> args) async {
|
||||||
|
// Set up logging
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(
|
||||||
|
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final parser = ArgumentParser()
|
||||||
|
..parser.addOption('to', help: 'The JID to send messages to');
|
||||||
|
final options = parser.handleArguments(args);
|
||||||
|
if (options == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
final jid = parser.jid;
|
||||||
|
final to = JID.fromString(options['to']! as String).toBare();
|
||||||
|
final connection = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
ExampleTCPSocketWrapper(parser.srvRecord),
|
||||||
|
)..connectionSettings = parser.connectionSettings;
|
||||||
|
|
||||||
|
// Generate OMEMO data
|
||||||
|
omemo.OmemoManager? oom;
|
||||||
|
final moxxmppOmemo = OmemoManager(
|
||||||
|
() async => oom!,
|
||||||
|
(toJid, _) async => toJid == to,
|
||||||
|
);
|
||||||
|
oom = omemo.OmemoManager(
|
||||||
|
await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5),
|
||||||
|
omemo.BlindTrustBeforeVerificationTrustManager(),
|
||||||
|
moxxmppOmemo.sendEmptyMessageImpl,
|
||||||
|
moxxmppOmemo.fetchDeviceList,
|
||||||
|
moxxmppOmemo.fetchDeviceBundle,
|
||||||
|
moxxmppOmemo.subscribeToDeviceListImpl,
|
||||||
|
moxxmppOmemo.publishDeviceImpl,
|
||||||
|
);
|
||||||
|
final deviceId = await oom.getDeviceId();
|
||||||
|
Logger.root.info('Our device id: $deviceId');
|
||||||
|
|
||||||
|
// Register the managers and negotiators
|
||||||
|
await connection.registerManagers([
|
||||||
|
PresenceManager(),
|
||||||
|
DiscoManager([]),
|
||||||
|
PubSubManager(),
|
||||||
|
MessageManager(),
|
||||||
|
moxxmppOmemo,
|
||||||
|
]);
|
||||||
|
await connection.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
StartTlsNegotiator(),
|
||||||
|
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set up event handlers
|
||||||
|
connection.asBroadcastStream().listen((event) {
|
||||||
|
if (event is MessageEvent) {
|
||||||
|
Logger.root.info(event.id);
|
||||||
|
Logger.root.info(event.extensions.keys.toList());
|
||||||
|
|
||||||
|
final body = event.encryptionError != null
|
||||||
|
? chalk.red('Failed to decrypt message: ${event.encryptionError}')
|
||||||
|
: chalk.green(event.get<MessageBodyData>()?.body ?? '');
|
||||||
|
print('[${event.from.toString()}] $body');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
Logger.root.info('Connecting...');
|
||||||
|
final result =
|
||||||
|
await connection.connect(shouldReconnect: false, waitUntilLogin: true);
|
||||||
|
if (!result.isType<bool>()) {
|
||||||
|
Logger.root.severe('Authentication failed!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.root.info('Connected.');
|
||||||
|
|
||||||
|
// Publish our bundle
|
||||||
|
Logger.root.info('Publishing bundle');
|
||||||
|
final device = await oom.getDevice();
|
||||||
|
final omemoResult = await moxxmppOmemo.publishBundle(await device.toBundle());
|
||||||
|
if (!omemoResult.isType<bool>()) {
|
||||||
|
Logger.root.severe(
|
||||||
|
'Failed to publish OMEMO bundle: ${omemoResult.get<OmemoError>()}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final repl = Repl(prompt: '> ');
|
||||||
|
await for (final line in repl.runAsync()) {
|
||||||
|
await connection
|
||||||
|
.getManagerById<MessageManager>(messageManager)!
|
||||||
|
.sendMessage(
|
||||||
|
to,
|
||||||
|
TypedMap<StanzaHandlerExtension>.fromList([
|
||||||
|
MessageBodyData(line),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
await connection.disconnect();
|
||||||
|
}
|
84
examples_dart/lib/arguments.dart
Normal file
84
examples_dart/lib/arguments.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:args/args.dart';
|
||||||
|
import 'package:chalkdart/chalk.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
|
extension StringToInt on String {
|
||||||
|
int toInt() => int.parse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around [ArgParser] for providing convenience functions and standard parameters
|
||||||
|
/// to the examples.
|
||||||
|
class ArgumentParser {
|
||||||
|
ArgumentParser() {
|
||||||
|
parser
|
||||||
|
..addOption('jid', help: 'The JID to connect as')
|
||||||
|
..addOption('password', help: 'The password to use for authenticating')
|
||||||
|
..addOption('host',
|
||||||
|
help:
|
||||||
|
'The host address to connect to (By default uses the domain part of the JID)')
|
||||||
|
..addOption('port', help: 'The port to connect to')
|
||||||
|
..addOption('xmpps-srv',
|
||||||
|
help:
|
||||||
|
'Inject a SRV record for _xmpps-client._tcp. Format: <priority>,<weight>,<target>,<port>')
|
||||||
|
..addFlag('help',
|
||||||
|
abbr: 'h',
|
||||||
|
negatable: false,
|
||||||
|
defaultsTo: false,
|
||||||
|
help: 'Show this help text');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [ArgParser] that handles parsing the arguments.
|
||||||
|
final ArgParser parser = ArgParser();
|
||||||
|
|
||||||
|
/// The parsed options. Only valid after calling [handleArguments].
|
||||||
|
late ArgResults options;
|
||||||
|
|
||||||
|
ArgResults? handleArguments(List<String> args) {
|
||||||
|
options = parser.parse(args);
|
||||||
|
if (options['help']!) {
|
||||||
|
print(parser.usage);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options['jid'] == null) {
|
||||||
|
print(chalk.red('No JID specified'));
|
||||||
|
print(parser.usage);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options['password'] == null) {
|
||||||
|
print(chalk.red('No password specified'));
|
||||||
|
print(parser.usage);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The JID to connect as.
|
||||||
|
JID get jid => JID.fromString(options['jid']!).toBare();
|
||||||
|
|
||||||
|
/// Construct connection settings from the parsed options.
|
||||||
|
ConnectionSettings get connectionSettings => ConnectionSettings(
|
||||||
|
jid: jid,
|
||||||
|
password: options['password']!,
|
||||||
|
host: options['host'],
|
||||||
|
port: (options['port'] as String?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Construct an xmpps-client SRV record for injection, if specified.
|
||||||
|
MoxSrvRecord? get srvRecord {
|
||||||
|
if (options['xmpps-srv'] == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final parts = options['xmpps-srv']!.split(',');
|
||||||
|
return MoxSrvRecord(
|
||||||
|
int.parse(parts[0]),
|
||||||
|
int.parse(parts[1]),
|
||||||
|
parts[2],
|
||||||
|
int.parse(parts[3]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
examples_dart/lib/socket.dart
Normal file
22
examples_dart/lib/socket.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
|
/// A simple socket for examples that allows injection of SRV records (since
|
||||||
|
/// we cannot use moxdns here).
|
||||||
|
class ExampleTCPSocketWrapper extends TCPSocketWrapper {
|
||||||
|
ExampleTCPSocketWrapper(this.srvRecord);
|
||||||
|
|
||||||
|
/// A potential SRV record to inject for testing.
|
||||||
|
final MoxSrvRecord? srvRecord;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool onBadCertificate(dynamic certificate, String domain) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
||||||
|
return [
|
||||||
|
if (srvRecord != null) srvRecord!,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
name: example_dart
|
name: example_dart
|
||||||
description: A sample command-line application.
|
description: A collection of samples for moxxmpp.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
# homepage: https://www.example.com
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.0 <3.0.0'
|
sdk: '>=2.18.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
args: 2.4.1
|
||||||
|
chalkdart: 2.0.9
|
||||||
|
cli_repl: 0.2.3
|
||||||
logging: ^1.0.2
|
logging: ^1.0.2
|
||||||
moxxmpp:
|
moxxmpp:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
@ -14,12 +16,19 @@ dependencies:
|
|||||||
moxxmpp_socket_tcp:
|
moxxmpp_socket_tcp:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
version: 0.3.1
|
version: 0.3.1
|
||||||
|
omemo_dart:
|
||||||
|
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||||
|
version: ^0.5.1
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
moxxmpp:
|
moxxmpp:
|
||||||
path: ../packages/moxxmpp
|
path: ../packages/moxxmpp
|
||||||
moxxmpp_socket_tcp:
|
moxxmpp_socket_tcp:
|
||||||
path: ../packages/moxxmpp_socket_tcp
|
path: ../packages/moxxmpp_socket_tcp
|
||||||
|
omemo_dart:
|
||||||
|
git:
|
||||||
|
url: https://github.com/PapaTutuWawa/omemo_dart.git
|
||||||
|
rev: 49c7e114e6cf80dcde55fbbd218bba3182045862
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
|
2
integration_tests/create_users.sh
Normal file
2
integration_tests/create_users.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
|
||||||
|
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123
|
@ -16,6 +16,8 @@
|
|||||||
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
|
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
|
||||||
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
|
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
|
||||||
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
|
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
|
||||||
|
- **BREAKING**: Overriding `BaseOmemoManager` is no longer required. `OmemoManager` now takes callback methods instead.
|
||||||
|
- Removed `ErrorResponseDiscoError` from the possible XEP-0030 errors.
|
||||||
|
|
||||||
## 0.3.1
|
## 0.3.1
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ export 'package:moxxmpp/src/settings.dart';
|
|||||||
export 'package:moxxmpp/src/socket.dart';
|
export 'package:moxxmpp/src/socket.dart';
|
||||||
export 'package:moxxmpp/src/stanza.dart';
|
export 'package:moxxmpp/src/stanza.dart';
|
||||||
export 'package:moxxmpp/src/stringxml.dart';
|
export 'package:moxxmpp/src/stringxml.dart';
|
||||||
export 'package:moxxmpp/src/types/result.dart';
|
|
||||||
export 'package:moxxmpp/src/util/typed_map.dart';
|
export 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||||
|
@ -25,11 +25,9 @@ import 'package:moxxmpp/src/settings.dart';
|
|||||||
import 'package:moxxmpp/src/socket.dart';
|
import 'package:moxxmpp/src/socket.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/util/queue.dart';
|
import 'package:moxxmpp/src/util/queue.dart';
|
||||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/types.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0352.dart';
|
import 'package:moxxmpp/src/xeps/xep_0352.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
@ -479,6 +477,7 @@ class XmppConnection {
|
|||||||
newStanza,
|
newStanza,
|
||||||
TypedMap(),
|
TypedMap(),
|
||||||
encrypted: details.encrypted,
|
encrypted: details.encrypted,
|
||||||
|
shouldEncrypt: details.shouldEncrypt,
|
||||||
forceEncryption: details.forceEncryption,
|
forceEncryption: details.forceEncryption,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -533,15 +532,14 @@ class XmppConnection {
|
|||||||
|
|
||||||
// Run post-send handlers
|
// Run post-send handlers
|
||||||
_log.fine('Running post stanza handlers..');
|
_log.fine('Running post stanza handlers..');
|
||||||
final extensions = TypedMap<StanzaHandlerExtension>()
|
|
||||||
..set(StreamManagementData(details.excludeFromStreamManagement));
|
|
||||||
await _runOutgoingPostStanzaHandlers(
|
await _runOutgoingPostStanzaHandlers(
|
||||||
newStanza,
|
newStanza,
|
||||||
initial: StanzaHandlerData(
|
initial: StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
newStanza,
|
newStanza,
|
||||||
extensions,
|
details.postSendExtensions ?? TypedMap<StanzaHandlerExtension>(),
|
||||||
|
encrypted: data.encrypted,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_log.fine('Done');
|
_log.fine('Done');
|
||||||
@ -736,6 +734,13 @@ class XmppConnection {
|
|||||||
: '';
|
: '';
|
||||||
_log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}');
|
_log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}');
|
||||||
|
|
||||||
|
if (incomingPreHandlers.skip) {
|
||||||
|
_log.fine(
|
||||||
|
'Not processing stanza (${incomingPreHandlers.stanza.tag}, ${incomingPreHandlers.stanza.id}) due to skip=true.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final awaited = await _stanzaAwaiter.onData(
|
final awaited = await _stanzaAwaiter.onData(
|
||||||
incomingPreHandlers.stanza,
|
incomingPreHandlers.stanza,
|
||||||
connectionSettings.jid.toBare(),
|
connectionSettings.jid.toBare(),
|
||||||
@ -753,6 +758,7 @@ class XmppConnection {
|
|||||||
incomingPreHandlers.stanza,
|
incomingPreHandlers.stanza,
|
||||||
incomingPreHandlers.extensions,
|
incomingPreHandlers.extensions,
|
||||||
encrypted: incomingPreHandlers.encrypted,
|
encrypted: incomingPreHandlers.encrypted,
|
||||||
|
encryptionError: incomingPreHandlers.encryptionError,
|
||||||
cancelReason: incomingPreHandlers.cancelReason,
|
cancelReason: incomingPreHandlers.cancelReason,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -70,9 +70,9 @@ class MessageEvent extends XmppEvent {
|
|||||||
MessageEvent(
|
MessageEvent(
|
||||||
this.from,
|
this.from,
|
||||||
this.to,
|
this.to,
|
||||||
this.id,
|
|
||||||
this.encrypted,
|
this.encrypted,
|
||||||
this.extensions, {
|
this.extensions, {
|
||||||
|
this.id,
|
||||||
this.type,
|
this.type,
|
||||||
this.error,
|
this.error,
|
||||||
this.encryptionError,
|
this.encryptionError,
|
||||||
@ -85,7 +85,7 @@ class MessageEvent extends XmppEvent {
|
|||||||
final JID to;
|
final JID to;
|
||||||
|
|
||||||
/// The id attribute of the message.
|
/// The id attribute of the message.
|
||||||
final String id;
|
final String? id;
|
||||||
|
|
||||||
/// The type attribute of the message.
|
/// The type attribute of the message.
|
||||||
final String? type;
|
final String? type;
|
||||||
|
@ -47,7 +47,6 @@ abstract class XmppManagerBase {
|
|||||||
|
|
||||||
final result = await dm!.discoInfoQuery(
|
final result = await dm!.discoInfoQuery(
|
||||||
_managerAttributes.getConnectionSettings().jid.toDomain(),
|
_managerAttributes.getConnectionSettings().jid.toDomain(),
|
||||||
shouldEncrypt: false,
|
|
||||||
);
|
);
|
||||||
if (result.isType<DiscoError>()) {
|
if (result.isType<DiscoError>()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -13,12 +13,20 @@ class StanzaHandlerData {
|
|||||||
this.encryptionError,
|
this.encryptionError,
|
||||||
this.encrypted = false,
|
this.encrypted = false,
|
||||||
this.forceEncryption = false,
|
this.forceEncryption = false,
|
||||||
|
this.shouldEncrypt = true,
|
||||||
|
this.skip = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Indicates to the runner that processing is now done. This means that all
|
/// Indicates to the runner that processing is now done. This means that all
|
||||||
/// pre-processing is done and no other handlers should be consulted.
|
/// pre-processing is done and no other handlers should be consulted.
|
||||||
bool done;
|
bool done;
|
||||||
|
|
||||||
|
/// Only useful in combination with [done] = true: When [skip] is set to true and
|
||||||
|
/// this [StanzaHandlerData] object is returned from a IncomingPreStanzaHandler, then
|
||||||
|
/// moxxmpp will skip checking whether the stanza was awaited and will not run any actual
|
||||||
|
/// IncomingStanzaHandler callbacks.
|
||||||
|
bool skip;
|
||||||
|
|
||||||
/// Indicates to the runner that processing is to be cancelled and no further handlers
|
/// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||||
/// should run. The stanza also will not be sent.
|
/// should run. The stanza also will not be sent.
|
||||||
bool cancel;
|
bool cancel;
|
||||||
@ -33,7 +41,7 @@ class StanzaHandlerData {
|
|||||||
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
||||||
Stanza stanza;
|
Stanza stanza;
|
||||||
|
|
||||||
/// Whether the stanza was received encrypted
|
/// Whether the stanza is already encrypted
|
||||||
bool encrypted;
|
bool encrypted;
|
||||||
|
|
||||||
// If true, forces the encryption manager to encrypt to the JID, even if it
|
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
@ -42,6 +50,10 @@ class StanzaHandlerData {
|
|||||||
// to the JID anyway.
|
// to the JID anyway.
|
||||||
bool forceEncryption;
|
bool forceEncryption;
|
||||||
|
|
||||||
|
/// Flag indicating whether a E2EE implementation should encrypt the stanza (true)
|
||||||
|
/// or not (false).
|
||||||
|
bool shouldEncrypt;
|
||||||
|
|
||||||
/// Additional data from other managers.
|
/// Additional data from other managers.
|
||||||
final TypedMap<StanzaHandlerExtension> extensions;
|
final TypedMap<StanzaHandlerExtension> extensions;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.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';
|
||||||
@ -100,10 +100,10 @@ class StanzaHandler extends Handler {
|
|||||||
matches &= firstTag?.xmlns == tagXmlns;
|
matches &= firstTag?.xmlns == tagXmlns;
|
||||||
}
|
}
|
||||||
} else if (tagXmlns != null) {
|
} else if (tagXmlns != null) {
|
||||||
matches &= listContains(
|
matches &= node.children.firstWhereOrNull(
|
||||||
node.children,
|
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||||
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
) !=
|
||||||
);
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
|
@ -73,16 +73,23 @@ class MessageManager extends XmppManagerBase {
|
|||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(
|
Future<StanzaHandlerData> _onMessage(
|
||||||
Stanza _,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
|
final body = stanza.firstTag('body');
|
||||||
|
if (body != null) {
|
||||||
|
state.extensions.set(
|
||||||
|
MessageBodyData(body.innerText()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
MessageEvent(
|
MessageEvent(
|
||||||
JID.fromString(state.stanza.attributes['from']! as String),
|
JID.fromString(state.stanza.attributes['from']! as String),
|
||||||
JID.fromString(state.stanza.attributes['to']! as String),
|
JID.fromString(state.stanza.attributes['to']! as String),
|
||||||
state.stanza.attributes['id']! as String,
|
|
||||||
state.encrypted,
|
state.encrypted,
|
||||||
state.extensions,
|
state.extensions,
|
||||||
|
id: state.stanza.attributes['id'] as String?,
|
||||||
type: state.stanza.attributes['type'] as String?,
|
type: state.stanza.attributes['type'] as String?,
|
||||||
error: StanzaError.fromStanza(state.stanza),
|
error: StanzaError.fromStanza(state.stanza),
|
||||||
encryptionError: state.encryptionError,
|
encryptionError: state.encryptionError,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
@ -8,7 +9,6 @@ import 'package:moxxmpp/src/managers/base.dart';
|
|||||||
import 'package:moxxmpp/src/settings.dart';
|
import 'package:moxxmpp/src/settings.dart';
|
||||||
import 'package:moxxmpp/src/socket.dart';
|
import 'package:moxxmpp/src/socket.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
|
|
||||||
/// The state a negotiator is currently in
|
/// The state a negotiator is currently in
|
||||||
enum NegotiatorState {
|
enum NegotiatorState {
|
||||||
@ -117,8 +117,7 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
/// Returns true if a feature in [features], which are the children of the
|
/// Returns true if a feature in [features], which are the children of the
|
||||||
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
return firstWhereOrNull(
|
return features.firstWhereOrNull(
|
||||||
features,
|
|
||||||
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
||||||
) !=
|
) !=
|
||||||
null;
|
null;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -10,7 +11,8 @@ import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.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/util/typed_map.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0198/types.dart';
|
||||||
|
|
||||||
/// A function that will be called when presence, outside of subscription request
|
/// A function that will be called when presence, outside of subscription request
|
||||||
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
@ -156,7 +158,9 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
bypassQueue: true,
|
bypassQueue: true,
|
||||||
excludeFromStreamManagement: true,
|
postSendExtensions: TypedMap<StanzaHandlerExtension>.fromList([
|
||||||
|
const StreamManagementData(true, null),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
@ -13,15 +13,13 @@ abstract class SaslNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
// Is SASL advertised?
|
// Is SASL advertised?
|
||||||
final mechanisms = firstWhereOrNull(
|
final mechanisms = features.firstWhereOrNull(
|
||||||
features,
|
|
||||||
(XMLNode feature) => feature.attributes['xmlns'] == saslXmlns,
|
(XMLNode feature) => feature.attributes['xmlns'] == saslXmlns,
|
||||||
);
|
);
|
||||||
if (mechanisms == null) return false;
|
if (mechanisms == null) return false;
|
||||||
|
|
||||||
// Is SASL PLAIN advertised?
|
// Is SASL PLAIN advertised?
|
||||||
return firstWhereOrNull(
|
return mechanisms.children.firstWhereOrNull(
|
||||||
mechanisms.children,
|
|
||||||
(XMLNode mechanism) => mechanism.text == mechanismName,
|
(XMLNode mechanism) => mechanism.text == mechanismName,
|
||||||
) !=
|
) !=
|
||||||
null;
|
null;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||||
import 'package:saslprep/saslprep.dart';
|
import 'package:saslprep/saslprep.dart';
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
import 'dart:math' show Random;
|
import 'dart:math' show Random;
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
@ -10,7 +11,6 @@ import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
|||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
|
|
||||||
enum _StartTlsState { ready, requested }
|
enum _StartTlsState { ready, requested }
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -14,7 +15,6 @@ import 'package:moxxmpp/src/roster/errors.dart';
|
|||||||
import 'package:moxxmpp/src/roster/state.dart';
|
import 'package:moxxmpp/src/roster/state.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';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class XmppRosterItem {
|
class XmppRosterItem {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
/// A description of a stanza to send.
|
/// A description of a stanza to send.
|
||||||
class StanzaDetails {
|
class StanzaDetails {
|
||||||
@ -7,10 +9,11 @@ class StanzaDetails {
|
|||||||
this.stanza, {
|
this.stanza, {
|
||||||
this.addId = true,
|
this.addId = true,
|
||||||
this.awaitable = true,
|
this.awaitable = true,
|
||||||
|
this.shouldEncrypt = true,
|
||||||
this.encrypted = false,
|
this.encrypted = false,
|
||||||
this.forceEncryption = false,
|
this.forceEncryption = false,
|
||||||
this.bypassQueue = false,
|
this.bypassQueue = false,
|
||||||
this.excludeFromStreamManagement = false,
|
this.postSendExtensions,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The stanza to send.
|
/// The stanza to send.
|
||||||
@ -22,9 +25,16 @@ class StanzaDetails {
|
|||||||
/// Track the stanza to allow awaiting its response.
|
/// Track the stanza to allow awaiting its response.
|
||||||
final bool awaitable;
|
final bool awaitable;
|
||||||
|
|
||||||
|
final bool forceEncryption;
|
||||||
|
|
||||||
|
/// Flag indicating whether the stanza that is sent is already encrypted (true)
|
||||||
|
/// or not (false). This is only useful for E2EE implementations that have to
|
||||||
|
/// send heartbeats that must bypass themselves.
|
||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
|
|
||||||
final bool forceEncryption;
|
/// Tells an E2EE implementation, if available, to encrypt the stanza (true) or
|
||||||
|
/// ignore the stanza (false).
|
||||||
|
final bool shouldEncrypt;
|
||||||
|
|
||||||
/// Bypasses being put into the queue. Useful for sending stanzas that must go out
|
/// Bypasses being put into the queue. Useful for sending stanzas that must go out
|
||||||
/// now, where it's okay if it does not get sent.
|
/// now, where it's okay if it does not get sent.
|
||||||
@ -34,31 +44,60 @@ class StanzaDetails {
|
|||||||
/// This makes the Stream Management implementation, when available, ignore the stanza,
|
/// This makes the Stream Management implementation, when available, ignore the stanza,
|
||||||
/// meaning that it gets counted but excluded from resending.
|
/// meaning that it gets counted but excluded from resending.
|
||||||
/// This should never have to be set to true.
|
/// This should never have to be set to true.
|
||||||
final bool excludeFromStreamManagement;
|
final TypedMap<StanzaHandlerExtension>? postSendExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple description of the <error /> element that may be inside a stanza
|
/// A general error type for errors.
|
||||||
class StanzaError {
|
abstract class StanzaError {
|
||||||
StanzaError(this.type, this.error);
|
static StanzaError? fromXMLNode(XMLNode node) {
|
||||||
String type;
|
final error = node.firstTag('error');
|
||||||
String error;
|
if (error == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final specificError = error.firstTagByXmlns(fullStanzaXmlns);
|
||||||
|
if (specificError == null) {
|
||||||
|
return UnknownStanzaError();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (specificError.tag) {
|
||||||
|
case RemoteServerNotFoundError.tag:
|
||||||
|
return RemoteServerNotFoundError();
|
||||||
|
case RemoteServerTimeoutError.tag:
|
||||||
|
return RemoteServerTimeoutError();
|
||||||
|
case ServiceUnavailableError.tag:
|
||||||
|
return ServiceUnavailableError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return UnknownStanzaError();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a StanzaError if [stanza] contains a <error /> element. If not, returns
|
|
||||||
/// null.
|
|
||||||
static StanzaError? fromStanza(Stanza stanza) {
|
static StanzaError? fromStanza(Stanza stanza) {
|
||||||
final error = stanza.firstTag('error');
|
return fromXMLNode(stanza);
|
||||||
if (error == null) return null;
|
|
||||||
|
|
||||||
final stanzaError = error.firstTagByXmlns(fullStanzaXmlns);
|
|
||||||
if (stanzaError == null) return null;
|
|
||||||
|
|
||||||
return StanzaError(
|
|
||||||
error.attributes['type']! as String,
|
|
||||||
stanzaError.tag,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recipient does not provide a given service.
|
||||||
|
/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-service-unavailable
|
||||||
|
class ServiceUnavailableError extends StanzaError {
|
||||||
|
static const tag = 'service-unavailable';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Could not connect to the remote server.
|
||||||
|
/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-remote-server-not-found
|
||||||
|
class RemoteServerNotFoundError extends StanzaError {
|
||||||
|
static const tag = 'remote-server-not-found';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The connection to the remote server timed out.
|
||||||
|
/// https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-remote-server-timeout
|
||||||
|
class RemoteServerTimeoutError extends StanzaError {
|
||||||
|
static const tag = 'remote-server-timeout';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An unknown error.
|
||||||
|
class UnknownStanzaError extends StanzaError {}
|
||||||
|
|
||||||
class Stanza extends XMLNode {
|
class Stanza extends XMLNode {
|
||||||
// ignore: use_super_parameters
|
// ignore: use_super_parameters
|
||||||
Stanza({
|
Stanza({
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
class Result<T, V> {
|
|
||||||
const Result(this._data)
|
|
||||||
: assert(
|
|
||||||
_data is T || _data is V,
|
|
||||||
'Invalid data type: Must be either $T or $V',
|
|
||||||
);
|
|
||||||
final dynamic _data;
|
|
||||||
|
|
||||||
bool isType<S>() => _data is S;
|
|
||||||
|
|
||||||
S get<S>() {
|
|
||||||
assert(_data is S, 'Data is not $S');
|
|
||||||
|
|
||||||
return _data as S;
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,7 +33,7 @@ class AsyncStanzaQueue {
|
|||||||
this._canSendCallback,
|
this._canSendCallback,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The lock for accessing [AsyncStanzaQueue._lock] and [AsyncStanzaQueue._running].
|
/// The lock for accessing [AsyncStanzaQueue._queue].
|
||||||
final Lock _lock = Lock();
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
/// The actual job queue.
|
/// The actual job queue.
|
||||||
@ -44,22 +44,15 @@ class AsyncStanzaQueue {
|
|||||||
|
|
||||||
final CanSendCallback _canSendCallback;
|
final CanSendCallback _canSendCallback;
|
||||||
|
|
||||||
/// Indicates whether we are currently executing a job.
|
|
||||||
bool _running = false;
|
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Queue<StanzaQueueEntry> get queue => _queue;
|
Queue<StanzaQueueEntry> get queue => _queue;
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
bool get isRunning => _running;
|
|
||||||
|
|
||||||
/// Adds a job [entry] to the queue.
|
/// Adds a job [entry] to the queue.
|
||||||
Future<void> enqueueStanza(StanzaQueueEntry entry) async {
|
Future<void> enqueueStanza(StanzaQueueEntry entry) async {
|
||||||
await _lock.synchronized(() async {
|
await _lock.synchronized(() async {
|
||||||
_queue.add(entry);
|
_queue.add(entry);
|
||||||
|
|
||||||
if (!_running && _queue.isNotEmpty && await _canSendCallback()) {
|
if (_queue.isNotEmpty && await _canSendCallback()) {
|
||||||
_running = true;
|
|
||||||
unawaited(
|
unawaited(
|
||||||
_runJob(_queue.removeFirst()),
|
_runJob(_queue.removeFirst()),
|
||||||
);
|
);
|
||||||
@ -79,8 +72,6 @@ class AsyncStanzaQueue {
|
|||||||
unawaited(
|
unawaited(
|
||||||
_runJob(_queue.removeFirst()),
|
_runJob(_queue.removeFirst()),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
_running = false;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -90,7 +81,6 @@ class AsyncStanzaQueue {
|
|||||||
|
|
||||||
await _lock.synchronized(() {
|
await _lock.synchronized(() {
|
||||||
if (_queue.isNotEmpty) {
|
if (_queue.isNotEmpty) {
|
||||||
_running = true;
|
|
||||||
unawaited(
|
unawaited(
|
||||||
_runJob(_queue.removeFirst()),
|
_runJob(_queue.removeFirst()),
|
||||||
);
|
);
|
||||||
|
@ -20,4 +20,6 @@ class TypedMap<B> {
|
|||||||
|
|
||||||
/// Return the object of type [T] from the map, if it has been stored.
|
/// Return the object of type [T] from the map, if it has been stored.
|
||||||
T? get<T>() => _data[T] as T?;
|
T? get<T>() => _data[T] as T?;
|
||||||
|
|
||||||
|
Iterable<Object> get keys => _data.keys;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ class DataForm {
|
|||||||
final List<List<DataFormField>> items;
|
final List<List<DataFormField>> items;
|
||||||
|
|
||||||
DataFormField? getFieldByVar(String varAttr) {
|
DataFormField? getFieldByVar(String varAttr) {
|
||||||
return firstWhereOrNull(fields, (field) => field.varAttr == varAttr);
|
return fields.firstWhereOrNull((field) => field.varAttr == varAttr);
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLNode toXml() {
|
XMLNode toXml() {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
abstract class DiscoError {}
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
|
||||||
|
/// Base type for disco-related errors.
|
||||||
|
abstract class DiscoError extends StanzaError {}
|
||||||
|
|
||||||
|
/// An unspecified error that is not covered by another [DiscoError].
|
||||||
class UnknownDiscoError extends DiscoError {}
|
class UnknownDiscoError extends DiscoError {}
|
||||||
|
|
||||||
|
/// The received disco response is invalid in some shape or form.
|
||||||
class InvalidResponseDiscoError extends DiscoError {}
|
class InvalidResponseDiscoError extends DiscoError {}
|
||||||
|
|
||||||
class ErrorResponseDiscoError extends DiscoError {}
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -9,7 +10,6 @@ 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/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/util/wait.dart';
|
import 'package:moxxmpp/src/util/wait.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/cache.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/cache.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
@ -44,11 +44,11 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {};
|
final Map<DiscoCacheKey, DiscoInfo> _discoInfoCache = {};
|
||||||
|
|
||||||
/// The tracker for tracking disco#info queries that are in flight.
|
/// The tracker for tracking disco#info queries that are in flight.
|
||||||
final WaitForTracker<DiscoCacheKey, Result<DiscoError, DiscoInfo>>
|
final WaitForTracker<DiscoCacheKey, Result<StanzaError, DiscoInfo>>
|
||||||
_discoInfoTracker = WaitForTracker();
|
_discoInfoTracker = WaitForTracker();
|
||||||
|
|
||||||
/// The tracker for tracking disco#info queries that are in flight.
|
/// The tracker for tracking disco#info queries that are in flight.
|
||||||
final WaitForTracker<DiscoCacheKey, Result<DiscoError, List<DiscoItem>>>
|
final WaitForTracker<DiscoCacheKey, Result<StanzaError, List<DiscoItem>>>
|
||||||
_discoItemsTracker = WaitForTracker();
|
_discoItemsTracker = WaitForTracker();
|
||||||
|
|
||||||
/// Cache lock
|
/// Cache lock
|
||||||
@ -67,7 +67,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
List<String> get features => _features;
|
List<String> get features => _features;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
WaitForTracker<DiscoCacheKey, Result<DiscoError, DiscoInfo>>
|
WaitForTracker<DiscoCacheKey, Result<StanzaError, DiscoInfo>>
|
||||||
get infoTracker => _discoInfoTracker;
|
get infoTracker => _discoInfoTracker;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -231,7 +231,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
|
|
||||||
Future<void> _exitDiscoInfoCriticalSection(
|
Future<void> _exitDiscoInfoCriticalSection(
|
||||||
DiscoCacheKey key,
|
DiscoCacheKey key,
|
||||||
Result<DiscoError, DiscoInfo> result,
|
Result<StanzaError, DiscoInfo> result,
|
||||||
bool shouldCache,
|
bool shouldCache,
|
||||||
) async {
|
) async {
|
||||||
await _cacheLock.synchronized(() async {
|
await _cacheLock.synchronized(() async {
|
||||||
@ -252,10 +252,10 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
///
|
///
|
||||||
/// [shouldCache] indicates whether the successful result of the disco#info query
|
/// [shouldCache] indicates whether the successful result of the disco#info query
|
||||||
/// should be cached (true) or not(false).
|
/// should be cached (true) or not(false).
|
||||||
Future<Result<DiscoError, DiscoInfo>> discoInfoQuery(
|
Future<Result<StanzaError, DiscoInfo>> discoInfoQuery(
|
||||||
JID entity, {
|
JID entity, {
|
||||||
String? node,
|
String? node,
|
||||||
bool shouldEncrypt = true,
|
bool shouldEncrypt = false,
|
||||||
bool shouldCache = true,
|
bool shouldCache = true,
|
||||||
}) async {
|
}) async {
|
||||||
DiscoInfo? info;
|
DiscoInfo? info;
|
||||||
@ -263,7 +263,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
final ecm = getAttributes()
|
final ecm = getAttributes()
|
||||||
.getManagerById<EntityCapabilitiesManager>(entityCapabilitiesManager);
|
.getManagerById<EntityCapabilitiesManager>(entityCapabilitiesManager);
|
||||||
final ffuture = await _cacheLock
|
final ffuture = await _cacheLock
|
||||||
.synchronized<Future<Future<Result<DiscoError, DiscoInfo>>?>?>(
|
.synchronized<Future<Future<Result<StanzaError, DiscoInfo>>?>?>(
|
||||||
() async {
|
() async {
|
||||||
// Check if we already know what the JID supports
|
// Check if we already know what the JID supports
|
||||||
if (_discoInfoCache.containsKey(cacheKey)) {
|
if (_discoInfoCache.containsKey(cacheKey)) {
|
||||||
@ -294,19 +294,21 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
final stanza = (await getAttributes().sendStanza(
|
final stanza = (await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
buildDiscoInfoQueryStanza(entity, node),
|
buildDiscoInfoQueryStanza(entity, node),
|
||||||
encrypted: !shouldEncrypt,
|
shouldEncrypt: shouldEncrypt,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
final query = stanza.firstTag('query');
|
|
||||||
if (query == null) {
|
// Error handling
|
||||||
final result = Result<DiscoError, DiscoInfo>(InvalidResponseDiscoError());
|
if (stanza.attributes['type'] == 'error') {
|
||||||
|
final result =
|
||||||
|
Result<StanzaError, DiscoInfo>(StanzaError.fromXMLNode(stanza));
|
||||||
await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache);
|
await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stanza.attributes['type'] == 'error') {
|
final query = stanza.firstTag('query');
|
||||||
//final error = stanza.firstTag('error');
|
if (query == null) {
|
||||||
final result = Result<DiscoError, DiscoInfo>(ErrorResponseDiscoError());
|
final result = Result<DiscoError, DiscoInfo>(InvalidResponseDiscoError());
|
||||||
await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache);
|
await _exitDiscoInfoCriticalSection(cacheKey, result, shouldCache);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -322,10 +324,10 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a disco items query to the (full) jid [entity], optionally with node=[node].
|
/// Sends a disco items query to the (full) jid [entity], optionally with node=[node].
|
||||||
Future<Result<DiscoError, List<DiscoItem>>> discoItemsQuery(
|
Future<Result<StanzaError, List<DiscoItem>>> discoItemsQuery(
|
||||||
JID entity, {
|
JID entity, {
|
||||||
String? node,
|
String? node,
|
||||||
bool shouldEncrypt = true,
|
bool shouldEncrypt = false,
|
||||||
}) async {
|
}) async {
|
||||||
final key = DiscoCacheKey(entity, node);
|
final key = DiscoCacheKey(entity, node);
|
||||||
final future = await _discoItemsTracker.waitFor(key);
|
final future = await _discoItemsTracker.waitFor(key);
|
||||||
@ -340,19 +342,18 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
final query = stanza.firstTag('query');
|
// Error handling
|
||||||
if (query == null) {
|
if (stanza.attributes['type'] == 'error') {
|
||||||
final result =
|
final result =
|
||||||
Result<DiscoError, List<DiscoItem>>(InvalidResponseDiscoError());
|
Result<StanzaError, List<DiscoItem>>(StanzaError.fromXMLNode(stanza));
|
||||||
await _discoItemsTracker.resolve(key, result);
|
await _discoItemsTracker.resolve(key, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stanza.attributes['type'] == 'error') {
|
final query = stanza.firstTag('query');
|
||||||
//final error = stanza.firstTag('error');
|
if (query == null) {
|
||||||
//print("Disco Items error: " + error.toXml());
|
|
||||||
final result =
|
final result =
|
||||||
Result<DiscoError, List<DiscoItem>>(ErrorResponseDiscoError());
|
Result<DiscoError, List<DiscoItem>>(InvalidResponseDiscoError());
|
||||||
await _discoItemsTracker.resolve(key, result);
|
await _discoItemsTracker.resolve(key, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -419,7 +420,7 @@ class DiscoManager extends XmppManagerBase {
|
|||||||
/// [entity] supports the disco feature [feature]. If not, returns false.
|
/// [entity] supports the disco feature [feature]. If not, returns false.
|
||||||
Future<bool> supportsFeature(JID entity, String feature) async {
|
Future<bool> supportsFeature(JID entity, String feature) async {
|
||||||
final info = await discoInfoQuery(entity);
|
final info = await discoInfoQuery(entity);
|
||||||
if (info.isType<DiscoError>()) return false;
|
if (info.isType<StanzaError>()) return false;
|
||||||
|
|
||||||
return info.get<DiscoInfo>().features.contains(feature);
|
return info.get<DiscoInfo>().features.contains(feature);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -7,7 +8,6 @@ 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/types/result.dart';
|
|
||||||
|
|
||||||
abstract class VCardError {}
|
abstract class VCardError {}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -9,7 +9,6 @@ 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/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0004.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/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
@ -202,6 +201,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -245,6 +245,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -329,6 +330,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
@ -419,6 +421,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -471,6 +474,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -521,6 +525,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (form.attributes['type'] != 'result') {
|
if (form.attributes['type'] != 'result') {
|
||||||
@ -550,6 +555,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (submit.attributes['type'] != 'result') {
|
if (submit.attributes['type'] != 'result') {
|
||||||
@ -580,6 +586,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -624,6 +631,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
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/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
@ -225,7 +225,6 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
final response = await disco.discoItemsQuery(
|
final response = await disco.discoItemsQuery(
|
||||||
jid,
|
jid,
|
||||||
node: userAvatarDataXmlns,
|
node: userAvatarDataXmlns,
|
||||||
shouldEncrypt: false,
|
|
||||||
);
|
);
|
||||||
if (response.isType<DiscoError>()) return Result(UnknownAvatarError());
|
if (response.isType<DiscoError>()) return Result(UnknownAvatarError());
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
@ -113,7 +113,7 @@ class StreamManagementNegotiator extends Sasl2FeatureNegotiator
|
|||||||
// We have to do this because we otherwise get a stanza stuck in the queue,
|
// We have to do this because we otherwise get a stanza stuck in the queue,
|
||||||
// thus spamming the server on every <a /> nonza we receive.
|
// thus spamming the server on every <a /> nonza we receive.
|
||||||
// ignore: cascade_invocations
|
// ignore: cascade_invocations
|
||||||
await sm.setState(StreamManagementState(0, 0));
|
await sm.setState(const StreamManagementState(0, 0));
|
||||||
await sm.commitState();
|
await sm.commitState();
|
||||||
|
|
||||||
_resumeFailed = true;
|
_resumeFailed = true;
|
||||||
|
@ -1,18 +1,43 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
part 'state.freezed.dart';
|
const _smNotSpecified = Object();
|
||||||
part 'state.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
@immutable
|
||||||
class StreamManagementState with _$StreamManagementState {
|
class StreamManagementState {
|
||||||
factory StreamManagementState(
|
const StreamManagementState(
|
||||||
int c2s,
|
this.c2s,
|
||||||
int s2c, {
|
this.s2c, {
|
||||||
String? streamResumptionLocation,
|
this.streamResumptionLocation,
|
||||||
String? streamResumptionId,
|
this.streamResumptionId,
|
||||||
}) = _StreamManagementState;
|
});
|
||||||
|
|
||||||
// JSON
|
/// The counter of stanzas sent from the client to the server.
|
||||||
factory StreamManagementState.fromJson(Map<String, dynamic> json) =>
|
final int c2s;
|
||||||
_$StreamManagementStateFromJson(json);
|
|
||||||
|
/// The counter of stanzas sent from the server to the client.
|
||||||
|
final int s2c;
|
||||||
|
|
||||||
|
/// If set, the server's preferred location for resumption.
|
||||||
|
final String? streamResumptionLocation;
|
||||||
|
|
||||||
|
/// If set, the token to allow using stream resumption.
|
||||||
|
final String? streamResumptionId;
|
||||||
|
|
||||||
|
StreamManagementState copyWith({
|
||||||
|
Object c2s = _smNotSpecified,
|
||||||
|
Object s2c = _smNotSpecified,
|
||||||
|
Object? streamResumptionLocation = _smNotSpecified,
|
||||||
|
Object? streamResumptionId = _smNotSpecified,
|
||||||
|
}) {
|
||||||
|
return StreamManagementState(
|
||||||
|
c2s != _smNotSpecified ? c2s as int : this.c2s,
|
||||||
|
s2c != _smNotSpecified ? s2c as int : this.s2c,
|
||||||
|
streamResumptionLocation: streamResumptionLocation != _smNotSpecified
|
||||||
|
? streamResumptionLocation as String?
|
||||||
|
: this.streamResumptionLocation,
|
||||||
|
streamResumptionId: streamResumptionId != _smNotSpecified
|
||||||
|
? streamResumptionId as String?
|
||||||
|
: this.streamResumptionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
// coverage:ignore-file
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'state.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
|
||||||
|
|
||||||
StreamManagementState _$StreamManagementStateFromJson(
|
|
||||||
Map<String, dynamic> json) {
|
|
||||||
return _StreamManagementState.fromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$StreamManagementState {
|
|
||||||
int get c2s => throw _privateConstructorUsedError;
|
|
||||||
int get s2c => throw _privateConstructorUsedError;
|
|
||||||
String? get streamResumptionLocation => throw _privateConstructorUsedError;
|
|
||||||
String? get streamResumptionId => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
$StreamManagementStateCopyWith<StreamManagementState> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class $StreamManagementStateCopyWith<$Res> {
|
|
||||||
factory $StreamManagementStateCopyWith(StreamManagementState value,
|
|
||||||
$Res Function(StreamManagementState) then) =
|
|
||||||
_$StreamManagementStateCopyWithImpl<$Res, StreamManagementState>;
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int c2s,
|
|
||||||
int s2c,
|
|
||||||
String? streamResumptionLocation,
|
|
||||||
String? streamResumptionId});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$StreamManagementStateCopyWithImpl<$Res,
|
|
||||||
$Val extends StreamManagementState>
|
|
||||||
implements $StreamManagementStateCopyWith<$Res> {
|
|
||||||
_$StreamManagementStateCopyWithImpl(this._value, this._then);
|
|
||||||
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Val _value;
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Res Function($Val) _then;
|
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? c2s = null,
|
|
||||||
Object? s2c = null,
|
|
||||||
Object? streamResumptionLocation = freezed,
|
|
||||||
Object? streamResumptionId = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(_value.copyWith(
|
|
||||||
c2s: null == c2s
|
|
||||||
? _value.c2s
|
|
||||||
: c2s // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
s2c: null == s2c
|
|
||||||
? _value.s2c
|
|
||||||
: s2c // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
streamResumptionLocation: freezed == streamResumptionLocation
|
|
||||||
? _value.streamResumptionLocation
|
|
||||||
: streamResumptionLocation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
streamResumptionId: freezed == streamResumptionId
|
|
||||||
? _value.streamResumptionId
|
|
||||||
: streamResumptionId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
) as $Val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class _$$_StreamManagementStateCopyWith<$Res>
|
|
||||||
implements $StreamManagementStateCopyWith<$Res> {
|
|
||||||
factory _$$_StreamManagementStateCopyWith(_$_StreamManagementState value,
|
|
||||||
$Res Function(_$_StreamManagementState) then) =
|
|
||||||
__$$_StreamManagementStateCopyWithImpl<$Res>;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{int c2s,
|
|
||||||
int s2c,
|
|
||||||
String? streamResumptionLocation,
|
|
||||||
String? streamResumptionId});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$$_StreamManagementStateCopyWithImpl<$Res>
|
|
||||||
extends _$StreamManagementStateCopyWithImpl<$Res, _$_StreamManagementState>
|
|
||||||
implements _$$_StreamManagementStateCopyWith<$Res> {
|
|
||||||
__$$_StreamManagementStateCopyWithImpl(_$_StreamManagementState _value,
|
|
||||||
$Res Function(_$_StreamManagementState) _then)
|
|
||||||
: super(_value, _then);
|
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? c2s = null,
|
|
||||||
Object? s2c = null,
|
|
||||||
Object? streamResumptionLocation = freezed,
|
|
||||||
Object? streamResumptionId = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(_$_StreamManagementState(
|
|
||||||
null == c2s
|
|
||||||
? _value.c2s
|
|
||||||
: c2s // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
null == s2c
|
|
||||||
? _value.s2c
|
|
||||||
: s2c // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,
|
|
||||||
streamResumptionLocation: freezed == streamResumptionLocation
|
|
||||||
? _value.streamResumptionLocation
|
|
||||||
: streamResumptionLocation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
streamResumptionId: freezed == streamResumptionId
|
|
||||||
? _value.streamResumptionId
|
|
||||||
: streamResumptionId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
class _$_StreamManagementState implements _StreamManagementState {
|
|
||||||
_$_StreamManagementState(this.c2s, this.s2c,
|
|
||||||
{this.streamResumptionLocation, this.streamResumptionId});
|
|
||||||
|
|
||||||
factory _$_StreamManagementState.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$$_StreamManagementStateFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
final int c2s;
|
|
||||||
@override
|
|
||||||
final int s2c;
|
|
||||||
@override
|
|
||||||
final String? streamResumptionLocation;
|
|
||||||
@override
|
|
||||||
final String? streamResumptionId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'StreamManagementState(c2s: $c2s, s2c: $s2c, streamResumptionLocation: $streamResumptionLocation, streamResumptionId: $streamResumptionId)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(dynamic other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _$_StreamManagementState &&
|
|
||||||
(identical(other.c2s, c2s) || other.c2s == c2s) &&
|
|
||||||
(identical(other.s2c, s2c) || other.s2c == s2c) &&
|
|
||||||
(identical(
|
|
||||||
other.streamResumptionLocation, streamResumptionLocation) ||
|
|
||||||
other.streamResumptionLocation == streamResumptionLocation) &&
|
|
||||||
(identical(other.streamResumptionId, streamResumptionId) ||
|
|
||||||
other.streamResumptionId == streamResumptionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType, c2s, s2c, streamResumptionLocation, streamResumptionId);
|
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$$_StreamManagementStateCopyWith<_$_StreamManagementState> get copyWith =>
|
|
||||||
__$$_StreamManagementStateCopyWithImpl<_$_StreamManagementState>(
|
|
||||||
this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$$_StreamManagementStateToJson(
|
|
||||||
this,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _StreamManagementState implements StreamManagementState {
|
|
||||||
factory _StreamManagementState(final int c2s, final int s2c,
|
|
||||||
{final String? streamResumptionLocation,
|
|
||||||
final String? streamResumptionId}) = _$_StreamManagementState;
|
|
||||||
|
|
||||||
factory _StreamManagementState.fromJson(Map<String, dynamic> json) =
|
|
||||||
_$_StreamManagementState.fromJson;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get c2s;
|
|
||||||
@override
|
|
||||||
int get s2c;
|
|
||||||
@override
|
|
||||||
String? get streamResumptionLocation;
|
|
||||||
@override
|
|
||||||
String? get streamResumptionId;
|
|
||||||
@override
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
_$$_StreamManagementStateCopyWith<_$_StreamManagementState> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'state.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_$_StreamManagementState _$$_StreamManagementStateFromJson(
|
|
||||||
Map<String, dynamic> json) =>
|
|
||||||
_$_StreamManagementState(
|
|
||||||
json['c2s'] as int,
|
|
||||||
json['s2c'] as int,
|
|
||||||
streamResumptionLocation: json['streamResumptionLocation'] as String?,
|
|
||||||
streamResumptionId: json['streamResumptionId'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$_StreamManagementStateToJson(
|
|
||||||
_$_StreamManagementState instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'c2s': instance.c2s,
|
|
||||||
's2c': instance.s2c,
|
|
||||||
'streamResumptionLocation': instance.streamResumptionLocation,
|
|
||||||
'streamResumptionId': instance.streamResumptionId,
|
|
||||||
};
|
|
@ -1,8 +1,39 @@
|
|||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
|
||||||
class StreamManagementData implements StanzaHandlerExtension {
|
class StreamManagementData implements StanzaHandlerExtension {
|
||||||
const StreamManagementData(this.exclude);
|
const StreamManagementData(this.exclude, this.queueId);
|
||||||
|
|
||||||
/// Whether the stanza should be exluded from the StreamManagement's resend queue.
|
/// Whether the stanza should be exluded from the StreamManagement's resend queue.
|
||||||
final bool exclude;
|
final bool exclude;
|
||||||
|
|
||||||
|
/// The ID to use when queuing the stanza.
|
||||||
|
final int? queueId;
|
||||||
|
|
||||||
|
/// If we resend a stanza, then we will have [queueId] set, so we should skip
|
||||||
|
/// incrementing the C2S counter.
|
||||||
|
bool get shouldCountStanza => queueId == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A queue element for keeping track of stanzas to (potentially) resend.
|
||||||
|
@immutable
|
||||||
|
class SMQueueEntry {
|
||||||
|
const SMQueueEntry(this.stanza, this.encrypted);
|
||||||
|
|
||||||
|
/// The actual stanza.
|
||||||
|
final Stanza stanza;
|
||||||
|
|
||||||
|
/// Flag indicating whether the stanza was encrypted before sending.
|
||||||
|
final bool encrypted;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is SMQueueEntry &&
|
||||||
|
other.stanza == stanza &&
|
||||||
|
other.encrypted == encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => stanza.hashCode ^ encrypted.hashCode;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import 'package:moxxmpp/src/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
||||||
@ -28,10 +29,10 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
}) : super(smManager);
|
}) : super(smManager);
|
||||||
|
|
||||||
/// The queue of stanzas that are not (yet) acked
|
/// The queue of stanzas that are not (yet) acked
|
||||||
final Map<int, Stanza> _unackedStanzas = {};
|
final Map<int, SMQueueEntry> _unackedStanzas = {};
|
||||||
|
|
||||||
/// Commitable state of the StreamManagementManager
|
/// Commitable state of the StreamManagementManager
|
||||||
StreamManagementState _state = StreamManagementState(0, 0);
|
StreamManagementState _state = const StreamManagementState(0, 0);
|
||||||
|
|
||||||
/// Mutex lock for _state
|
/// Mutex lock for _state
|
||||||
final Lock _stateLock = Lock();
|
final Lock _stateLock = Lock();
|
||||||
@ -61,7 +62,7 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Functions for testing
|
/// Functions for testing
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Map<int, Stanza> getUnackedStanzas() => _unackedStanzas;
|
Map<int, SMQueueEntry> getUnackedStanzas() => _unackedStanzas;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Future<int> getPendingAcks() async {
|
Future<int> getPendingAcks() async {
|
||||||
@ -306,6 +307,10 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
if (_pendingAcks > 0) {
|
if (_pendingAcks > 0) {
|
||||||
// Prevent diff from becoming negative
|
// Prevent diff from becoming negative
|
||||||
final diff = max(_state.c2s - h, 0);
|
final diff = max(_state.c2s - h, 0);
|
||||||
|
|
||||||
|
logger.finest(
|
||||||
|
'Setting _pendingAcks to $diff (was $_pendingAcks before): max(${_state.c2s} - $h, 0)',
|
||||||
|
);
|
||||||
_pendingAcks = diff;
|
_pendingAcks = diff;
|
||||||
|
|
||||||
// Reset the timer
|
// Reset the timer
|
||||||
@ -336,15 +341,18 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final sequences = _unackedStanzas.keys.toList()..sort();
|
final sequences = _unackedStanzas.keys.toList()..sort();
|
||||||
for (final height in sequences) {
|
for (final height in sequences) {
|
||||||
|
logger.finest('Unacked stanza: height $height, h $h');
|
||||||
|
|
||||||
// Do nothing if the ack does not concern this stanza
|
// Do nothing if the ack does not concern this stanza
|
||||||
if (height > h) continue;
|
if (height > h) continue;
|
||||||
|
|
||||||
final stanza = _unackedStanzas[height]!;
|
logger.finest('Removing stanza with height $height');
|
||||||
|
final entry = _unackedStanzas[height]!;
|
||||||
_unackedStanzas.remove(height);
|
_unackedStanzas.remove(height);
|
||||||
|
|
||||||
// Create a StanzaAckedEvent if the stanza is correct
|
// Create a StanzaAckedEvent if the stanza is correct
|
||||||
if (shouldTriggerAckedEvent(stanza)) {
|
if (shouldTriggerAckedEvent(entry.stanza)) {
|
||||||
attrs.sendEvent(StanzaAckedEvent(stanza));
|
attrs.sendEvent(StanzaAckedEvent(entry.stanza));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,13 +409,29 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
if (isStreamManagementEnabled()) {
|
if (isStreamManagementEnabled()) {
|
||||||
await _incrementC2S();
|
final smData = state.extensions.get<StreamManagementData>();
|
||||||
|
logger.finest('Should count stanza: ${smData?.shouldCountStanza}');
|
||||||
|
if (smData?.shouldCountStanza ?? true) {
|
||||||
|
await _incrementC2S();
|
||||||
|
}
|
||||||
|
|
||||||
if (state.extensions.get<StreamManagementData>()?.exclude ?? false) {
|
if (smData?.exclude ?? false) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
_unackedStanzas[_state.c2s] = stanza;
|
int queueId;
|
||||||
|
if (smData?.queueId != null) {
|
||||||
|
logger.finest('Reusing queue id ${smData!.queueId}');
|
||||||
|
queueId = smData.queueId!;
|
||||||
|
} else {
|
||||||
|
queueId = await _stateLock.synchronized(() => _state.c2s);
|
||||||
|
}
|
||||||
|
|
||||||
|
_unackedStanzas[queueId] = SMQueueEntry(
|
||||||
|
stanza,
|
||||||
|
// Prevent an E2EE message being encrypted again
|
||||||
|
state.encrypted,
|
||||||
|
);
|
||||||
await _sendAckRequest();
|
await _sendAckRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,16 +439,23 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resendStanzas() async {
|
Future<void> _resendStanzas() async {
|
||||||
final stanzas = _unackedStanzas.values.toList();
|
final queueCopy = _unackedStanzas.entries.toList();
|
||||||
_unackedStanzas.clear();
|
for (final entry in queueCopy) {
|
||||||
|
logger.finest(
|
||||||
for (final stanza in stanzas) {
|
'Resending ${entry.value.stanza.tag} with id ${entry.value.stanza.attributes["id"]}',
|
||||||
logger
|
);
|
||||||
.finest('Resending ${stanza.tag} with id ${stanza.attributes["id"]}');
|
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
stanza,
|
entry.value.stanza,
|
||||||
|
postSendExtensions: TypedMap<StanzaHandlerExtension>.fromList([
|
||||||
|
StreamManagementData(
|
||||||
|
false,
|
||||||
|
entry.key,
|
||||||
|
),
|
||||||
|
]),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
|
// Prevent an E2EE message being encrypted again
|
||||||
|
encrypted: entry.value.encrypted,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0386.dart';
|
import 'package:moxxmpp/src/xeps/xep_0386.dart';
|
||||||
|
|
||||||
class CSIActiveNonza extends XMLNode {
|
class CSIActiveNonza extends XMLNode {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
@ -7,7 +8,6 @@ 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/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
@ -58,10 +58,10 @@ class HttpFileUploadManager extends XmppManagerBase {
|
|||||||
/// Returns whether the entity provided an identity that tells us that we can ask it
|
/// Returns whether the entity provided an identity that tells us that we can ask it
|
||||||
/// for an HTTP upload slot.
|
/// for an HTTP upload slot.
|
||||||
bool _containsFileUploadIdentity(DiscoInfo info) {
|
bool _containsFileUploadIdentity(DiscoInfo info) {
|
||||||
return listContains(
|
return info.identities.firstWhereOrNull(
|
||||||
info.identities,
|
(Identity id) => id.category == 'store' && id.type == 'file',
|
||||||
(Identity id) => id.category == 'store' && id.type == 'file',
|
) !=
|
||||||
);
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the maximum filesize in octets from the disco response. Returns null
|
/// Extract the maximum filesize in octets from the disco response. Returns null
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:omemo_dart/omemo_dart.dart';
|
import 'package:omemo_dart/omemo_dart.dart';
|
||||||
|
|
||||||
/// A simple wrapper class for defining elements that should not be encrypted.
|
/// A simple wrapper class for defining elements that should not be encrypted.
|
||||||
@ -13,9 +14,16 @@ class DoNotEncrypt {
|
|||||||
|
|
||||||
/// An encryption error caused by OMEMO.
|
/// An encryption error caused by OMEMO.
|
||||||
class OmemoEncryptionError {
|
class OmemoEncryptionError {
|
||||||
const OmemoEncryptionError(this.jids, this.devices);
|
const OmemoEncryptionError(this.deviceEncryptionErrors);
|
||||||
|
|
||||||
/// See omemo_dart's EncryptionResult for info on these fields.
|
/// See omemo_dart's EncryptionResult for info on this field.
|
||||||
final Map<String, OmemoException> jids;
|
final Map<String, List<EncryptToJidError>> deviceEncryptionErrors;
|
||||||
final Map<RatchetMapKey, OmemoException> devices;
|
}
|
||||||
|
|
||||||
|
class OmemoData extends StanzaHandlerExtension {
|
||||||
|
OmemoData(this.newRatchets, this.replacedRatchets);
|
||||||
|
|
||||||
|
final Map<String, List<int>> newRatchets;
|
||||||
|
|
||||||
|
final Map<String, List<int>> replacedRatchets;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -10,13 +11,11 @@ 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/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0203.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0280.dart';
|
import 'package:moxxmpp/src/xeps/xep_0280.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
||||||
@ -24,9 +23,21 @@ import 'package:moxxmpp/src/xeps/xep_0384/crypto.dart';
|
|||||||
import 'package:moxxmpp/src/xeps/xep_0384/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0384/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
|
import 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
||||||
import 'package:omemo_dart/omemo_dart.dart';
|
import 'package:omemo_dart/omemo_dart.dart' as omemo;
|
||||||
import 'package:xml/xml.dart';
|
import 'package:xml/xml.dart';
|
||||||
|
|
||||||
|
/// A callback that is executed whenever we need to acquire the OmemoManager backing
|
||||||
|
/// the manager.
|
||||||
|
typedef GetOmemoManagerCallback = Future<omemo.OmemoManager> Function();
|
||||||
|
|
||||||
|
/// A callback for figuring out whether a stanza should be encrypted or not. Note that
|
||||||
|
/// returning true here does not necessarily mean that a stanza gets encrypted because
|
||||||
|
/// handlers can indicate that a stanza should not be encrypted, e.g. PubSub.
|
||||||
|
typedef ShouldEncryptStanzaCallback = Future<bool> Function(
|
||||||
|
JID toJid,
|
||||||
|
Stanza stanza,
|
||||||
|
);
|
||||||
|
|
||||||
const _doNotEncryptList = [
|
const _doNotEncryptList = [
|
||||||
// XEP-0033
|
// XEP-0033
|
||||||
DoNotEncrypt('addresses', extendedAddressingXmlns),
|
DoNotEncrypt('addresses', extendedAddressingXmlns),
|
||||||
@ -43,8 +54,15 @@ const _doNotEncryptList = [
|
|||||||
DoNotEncrypt('stanza-id', stableIdXmlns),
|
DoNotEncrypt('stanza-id', stableIdXmlns),
|
||||||
];
|
];
|
||||||
|
|
||||||
abstract class BaseOmemoManager extends XmppManagerBase {
|
class OmemoManager extends XmppManagerBase {
|
||||||
BaseOmemoManager() : super(omemoManager);
|
OmemoManager(this._getOmemoManager, this._shouldEncryptStanza)
|
||||||
|
: super(omemoManager);
|
||||||
|
|
||||||
|
/// Callback for getting the [omemo.OmemoManager].
|
||||||
|
final GetOmemoManagerCallback _getOmemoManager;
|
||||||
|
|
||||||
|
/// Callback for checking whether a stanza should be encrypted or not.
|
||||||
|
final ShouldEncryptStanzaCallback _shouldEncryptStanza;
|
||||||
|
|
||||||
// TODO(Unknown): Technically, this is not always true
|
// TODO(Unknown): Technically, this is not always true
|
||||||
@override
|
@override
|
||||||
@ -113,22 +131,19 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tell the OmemoManager
|
// Tell the OmemoManager
|
||||||
(await getOmemoManager()).onDeviceListUpdate(jid.toString(), ids);
|
await (await _getOmemoManager()).onDeviceListUpdate(jid.toString(), ids);
|
||||||
|
|
||||||
// Generate an event
|
// Generate an event
|
||||||
getAttributes().sendEvent(OmemoDeviceListUpdatedEvent(jid, ids));
|
getAttributes().sendEvent(OmemoDeviceListUpdatedEvent(jid, ids));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForOverriding
|
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
||||||
Future<OmemoManager> getOmemoManager();
|
Future<int> _getDeviceId() async => (await _getOmemoManager()).getDeviceId();
|
||||||
|
|
||||||
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
||||||
Future<int> _getDeviceId() async => (await getOmemoManager()).getDeviceId();
|
Future<omemo.OmemoBundle> _getDeviceBundle() async {
|
||||||
|
final om = await _getOmemoManager();
|
||||||
/// Wrapper around using getSessionManager and then calling getDeviceId on it.
|
|
||||||
Future<OmemoBundle> _getDeviceBundle() async {
|
|
||||||
final om = await getOmemoManager();
|
|
||||||
final device = await om.getDevice();
|
final device = await om.getDevice();
|
||||||
return device.toBundle();
|
return device.toBundle();
|
||||||
}
|
}
|
||||||
@ -199,53 +214,45 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
XMLNode _buildEncryptedElement(
|
XMLNode _buildEncryptedElement(
|
||||||
EncryptionResult result,
|
omemo.EncryptionResult result,
|
||||||
String recipientJid,
|
String recipientJid,
|
||||||
int deviceId,
|
int deviceId,
|
||||||
) {
|
) {
|
||||||
final keyElements = <String, List<XMLNode>>{};
|
final keyElements = <String, List<XMLNode>>{};
|
||||||
for (final key in result.encryptedKeys) {
|
for (final keys in result.encryptedKeys.entries) {
|
||||||
final keyElement = XMLNode(
|
keyElements[keys.key] = keys.value
|
||||||
tag: 'key',
|
.map(
|
||||||
attributes: <String, String>{
|
(ek) => XMLNode(
|
||||||
'rid': '${key.rid}',
|
tag: 'key',
|
||||||
'kex': key.kex ? 'true' : 'false',
|
attributes: {
|
||||||
},
|
'rid': ek.rid.toString(),
|
||||||
text: key.value,
|
if (ek.kex) 'kex': 'true',
|
||||||
);
|
},
|
||||||
|
text: ek.value,
|
||||||
if (keyElements.containsKey(key.jid)) {
|
),
|
||||||
keyElements[key.jid]!.add(keyElement);
|
)
|
||||||
} else {
|
.toList();
|
||||||
keyElements[key.jid] = [keyElement];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final keysElements = keyElements.entries.map((entry) {
|
final keysElements = keyElements.entries.map((entry) {
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'keys',
|
tag: 'keys',
|
||||||
attributes: <String, String>{
|
attributes: {
|
||||||
'jid': entry.key,
|
'jid': entry.key,
|
||||||
},
|
},
|
||||||
children: entry.value,
|
children: entry.value,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
var payloadElement = <XMLNode>[];
|
|
||||||
if (result.ciphertext != null) {
|
|
||||||
payloadElement = [
|
|
||||||
XMLNode(
|
|
||||||
tag: 'payload',
|
|
||||||
text: base64.encode(result.ciphertext!),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'encrypted',
|
tag: 'encrypted',
|
||||||
xmlns: omemoXmlns,
|
xmlns: omemoXmlns,
|
||||||
children: [
|
children: [
|
||||||
...payloadElement,
|
if (result.ciphertext != null)
|
||||||
|
XMLNode(
|
||||||
|
tag: 'payload',
|
||||||
|
text: base64Encode(result.ciphertext!),
|
||||||
|
),
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'header',
|
tag: 'header',
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
@ -259,7 +266,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// For usage with omemo_dart's OmemoManager.
|
/// For usage with omemo_dart's OmemoManager.
|
||||||
Future<void> sendEmptyMessageImpl(
|
Future<void> sendEmptyMessageImpl(
|
||||||
EncryptionResult result,
|
omemo.EncryptionResult result,
|
||||||
String toJid,
|
String toJid,
|
||||||
) async {
|
) async {
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
@ -288,7 +295,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Send a heartbeat message to [jid].
|
/// Send a heartbeat message to [jid].
|
||||||
Future<void> sendOmemoHeartbeat(String jid) async {
|
Future<void> sendOmemoHeartbeat(String jid) async {
|
||||||
final om = await getOmemoManager();
|
final om = await _getOmemoManager();
|
||||||
await om.sendOmemoHeartbeat(jid);
|
await om.sendOmemoHeartbeat(jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,17 +308,22 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// For usage with omemo_dart's OmemoManager
|
/// For usage with omemo_dart's OmemoManager
|
||||||
Future<OmemoBundle?> fetchDeviceBundle(String jid, int id) async {
|
Future<omemo.OmemoBundle?> fetchDeviceBundle(String jid, int id) async {
|
||||||
final result = await retrieveDeviceBundle(JID.fromString(jid), id);
|
final result = await retrieveDeviceBundle(JID.fromString(jid), id);
|
||||||
if (result.isType<OmemoError>()) return null;
|
if (result.isType<OmemoError>()) return null;
|
||||||
|
|
||||||
return result.get<OmemoBundle>();
|
return result.get<omemo.OmemoBundle>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onOutgoingStanza(
|
Future<StanzaHandlerData> _onOutgoingStanza(
|
||||||
Stanza stanza,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
|
if (!state.shouldEncrypt) {
|
||||||
|
logger.finest('Not encrypting since state.shouldEncrypt is false');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.encrypted) {
|
if (state.encrypted) {
|
||||||
logger.finest('Not encrypting since state.encrypted is true');
|
logger.finest('Not encrypting since state.encrypted is true');
|
||||||
return state;
|
return state;
|
||||||
@ -324,7 +336,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final toJid = JID.fromString(stanza.to!).toBare();
|
final toJid = JID.fromString(stanza.to!).toBare();
|
||||||
final shouldEncryptResult = await shouldEncryptStanza(toJid, stanza);
|
final shouldEncryptResult = await _shouldEncryptStanza(toJid, stanza);
|
||||||
if (!shouldEncryptResult && !state.forceEncryption) {
|
if (!shouldEncryptResult && !state.forceEncryption) {
|
||||||
logger.finest(
|
logger.finest(
|
||||||
'Not encrypting stanza for $toJid: Both shouldEncryptStanza and forceEncryption are false.',
|
'Not encrypting stanza for $toJid: Both shouldEncryptStanza and forceEncryption are false.',
|
||||||
@ -351,29 +363,29 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
.getManagerById<CarbonsManager>(carbonsManager)
|
.getManagerById<CarbonsManager>(carbonsManager)
|
||||||
?.isEnabled ??
|
?.isEnabled ??
|
||||||
false;
|
false;
|
||||||
final om = await getOmemoManager();
|
final om = await _getOmemoManager();
|
||||||
|
final encryptToJids = [
|
||||||
|
toJid.toString(),
|
||||||
|
if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(),
|
||||||
|
];
|
||||||
final result = await om.onOutgoingStanza(
|
final result = await om.onOutgoingStanza(
|
||||||
OmemoOutgoingStanza(
|
omemo.OmemoOutgoingStanza(
|
||||||
[
|
encryptToJids,
|
||||||
toJid.toString(),
|
|
||||||
if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(),
|
|
||||||
],
|
|
||||||
_buildEnvelope(toEncrypt, toJid.toString()),
|
_buildEnvelope(toEncrypt, toJid.toString()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
logger.finest('Encryption done');
|
logger.finest('Encryption done');
|
||||||
|
|
||||||
if (!result.isSuccess(2)) {
|
if (!result.canSend) {
|
||||||
return state
|
return state
|
||||||
..cancel = true
|
..cancel = true
|
||||||
// If we have no device list for toJid, then the contact most likely does not
|
// If we have no device list for toJid, then the contact most likely does not
|
||||||
// support OMEMO:2
|
// support OMEMO:2
|
||||||
..cancelReason = result.jidEncryptionErrors[toJid.toString()]
|
..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first
|
||||||
is NoKeyMaterialAvailableException
|
.error is omemo.NoKeyMaterialAvailableError
|
||||||
? OmemoNotSupportedForContactException()
|
? OmemoNotSupportedForContactException()
|
||||||
: UnknownOmemoError()
|
: UnknownOmemoError()
|
||||||
..encryptionError = OmemoEncryptionError(
|
..encryptionError = OmemoEncryptionError(
|
||||||
result.jidEncryptionErrors,
|
|
||||||
result.deviceEncryptionErrors,
|
result.deviceEncryptionErrors,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -401,53 +413,45 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
..encrypted = true;
|
..encrypted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is called whenever a message is to be encrypted. If it returns true,
|
|
||||||
/// then the message will be encrypted. If it returns false, the message won't be
|
|
||||||
/// encrypted.
|
|
||||||
@visibleForOverriding
|
|
||||||
Future<bool> shouldEncryptStanza(JID toJid, Stanza stanza);
|
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onIncomingStanza(
|
Future<StanzaHandlerData> _onIncomingStanza(
|
||||||
Stanza stanza,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns);
|
|
||||||
if (encrypted == null) return state;
|
|
||||||
if (stanza.from == null) return state;
|
if (stanza.from == null) return state;
|
||||||
|
|
||||||
|
final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns)!;
|
||||||
final fromJid = JID.fromString(stanza.from!).toBare();
|
final fromJid = JID.fromString(stanza.from!).toBare();
|
||||||
final header = encrypted.firstTag('header')!;
|
final header = encrypted.firstTag('header')!;
|
||||||
final payloadElement = encrypted.firstTag('payload');
|
final ourJid = getAttributes().getFullJID();
|
||||||
final keys = List<EncryptedKey>.empty(growable: true);
|
final ourJidString = ourJid.toBare().toString();
|
||||||
|
final keys = List<omemo.EncryptedKey>.empty(growable: true);
|
||||||
for (final keysElement in header.findTags('keys')) {
|
for (final keysElement in header.findTags('keys')) {
|
||||||
|
// We only care about our own JID
|
||||||
final jid = keysElement.attributes['jid']! as String;
|
final jid = keysElement.attributes['jid']! as String;
|
||||||
for (final key in keysElement.findTags('key')) {
|
if (jid != ourJidString) {
|
||||||
keys.add(
|
continue;
|
||||||
EncryptedKey(
|
|
||||||
jid,
|
|
||||||
int.parse(key.attributes['rid']! as String),
|
|
||||||
key.innerText(),
|
|
||||||
key.attributes['kex'] == 'true',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keys.addAll(
|
||||||
|
keysElement.findTags('key').map(
|
||||||
|
(key) => omemo.EncryptedKey(
|
||||||
|
int.parse(key.attributes['rid']! as String),
|
||||||
|
key.innerText(),
|
||||||
|
key.attributes['kex'] == 'true',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ourJid = getAttributes().getFullJID();
|
|
||||||
final sid = int.parse(header.attributes['sid']! as String);
|
final sid = int.parse(header.attributes['sid']! as String);
|
||||||
|
final om = await _getOmemoManager();
|
||||||
final om = await getOmemoManager();
|
|
||||||
final result = await om.onIncomingStanza(
|
final result = await om.onIncomingStanza(
|
||||||
OmemoIncomingStanza(
|
omemo.OmemoIncomingStanza(
|
||||||
fromJid.toString(),
|
fromJid.toString(),
|
||||||
sid,
|
sid,
|
||||||
state.extensions
|
|
||||||
.get<DelayedDeliveryData>()
|
|
||||||
?.timestamp
|
|
||||||
.millisecondsSinceEpoch ??
|
|
||||||
DateTime.now().millisecondsSinceEpoch,
|
|
||||||
keys,
|
keys,
|
||||||
payloadElement?.innerText(),
|
encrypted.firstTag('payload')?.innerText(),
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -464,6 +468,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.finest('Got payload: ${result.payload != null}');
|
||||||
if (result.payload != null) {
|
if (result.payload != null) {
|
||||||
XMLNode envelope;
|
XMLNode envelope;
|
||||||
try {
|
try {
|
||||||
@ -481,6 +486,8 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
// Do not add forbidden elements from the envelope
|
// Do not add forbidden elements from the envelope
|
||||||
envelopeChildren.where(shouldEncryptElement),
|
envelopeChildren.where(shouldEncryptElement),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.finest('Adding children: ${envelopeChildren.map((c) => c.tag)}');
|
||||||
} else {
|
} else {
|
||||||
logger.warning('Invalid envelope element: No <content /> element');
|
logger.warning('Invalid envelope element: No <content /> element');
|
||||||
}
|
}
|
||||||
@ -490,6 +497,15 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore heartbeat messages
|
||||||
|
if (stanza.tag == 'message' && encrypted.firstTag('payload') == null) {
|
||||||
|
logger.finest('Received empty OMEMO message. Ending processing early.');
|
||||||
|
return state
|
||||||
|
..encrypted = true
|
||||||
|
..skip = true
|
||||||
|
..done = true;
|
||||||
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
..encrypted = true
|
..encrypted = true
|
||||||
..stanza = Stanza(
|
..stanza = Stanza(
|
||||||
@ -500,6 +516,12 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
children: children,
|
children: children,
|
||||||
tag: stanza.tag,
|
tag: stanza.tag,
|
||||||
attributes: Map<String, String>.from(stanza.attributes),
|
attributes: Map<String, String>.from(stanza.attributes),
|
||||||
|
)
|
||||||
|
..extensions.set<OmemoData>(
|
||||||
|
OmemoData(
|
||||||
|
result.newRatchets,
|
||||||
|
result.replacedRatchets,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +554,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
/// Retrieve all device bundles for the JID [jid].
|
/// Retrieve all device bundles for the JID [jid].
|
||||||
///
|
///
|
||||||
/// On success, returns a list of devices. On failure, returns am OmemoError.
|
/// On success, returns a list of devices. On failure, returns am OmemoError.
|
||||||
Future<Result<OmemoError, List<OmemoBundle>>> retrieveDeviceBundles(
|
Future<Result<OmemoError, List<omemo.OmemoBundle>>> retrieveDeviceBundles(
|
||||||
JID jid,
|
JID jid,
|
||||||
) async {
|
) async {
|
||||||
// TODO(Unknown): Should we query the device list first?
|
// TODO(Unknown): Should we query the device list first?
|
||||||
@ -553,7 +575,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
/// Retrieves a bundle from entity [jid] with the device id [deviceId].
|
/// Retrieves a bundle from entity [jid] with the device id [deviceId].
|
||||||
///
|
///
|
||||||
/// On success, returns the device bundle. On failure, returns an OmemoError.
|
/// On success, returns the device bundle. On failure, returns an OmemoError.
|
||||||
Future<Result<OmemoError, OmemoBundle>> retrieveDeviceBundle(
|
Future<Result<OmemoError, omemo.OmemoBundle>> retrieveDeviceBundle(
|
||||||
JID jid,
|
JID jid,
|
||||||
int deviceId,
|
int deviceId,
|
||||||
) async {
|
) async {
|
||||||
@ -569,7 +591,9 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
/// nodes.
|
/// nodes.
|
||||||
///
|
///
|
||||||
/// On success, returns true. On failure, returns an OmemoError.
|
/// On success, returns true. On failure, returns an OmemoError.
|
||||||
Future<Result<OmemoError, bool>> publishBundle(OmemoBundle bundle) async {
|
Future<Result<OmemoError, bool>> publishBundle(
|
||||||
|
omemo.OmemoBundle bundle,
|
||||||
|
) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final pm = attrs.getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = attrs.getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final bareJid = attrs.getFullJID().toBare();
|
final bareJid = attrs.getFullJID().toBare();
|
||||||
@ -636,6 +660,11 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns);
|
await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implementation for publishing our device [device].
|
||||||
|
Future<void> publishDeviceImpl(omemo.OmemoDevice device) async {
|
||||||
|
await publishBundle(await device.toBundle());
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to find out if [jid] supports omemo:2.
|
/// Attempts to find out if [jid] supports omemo:2.
|
||||||
///
|
///
|
||||||
/// On success, returns whether [jid] has published a device list and device bundles.
|
/// On success, returns whether [jid] has published a device list and device bundles.
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
|
|
||||||
/// A special type of [XmppFeatureNegotiatorBase] that is aware of SASL2.
|
/// A special type of [XmppFeatureNegotiatorBase] that is aware of SASL2.
|
||||||
abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
import 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.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';
|
||||||
@ -114,8 +114,7 @@ class StatelessFileSharingData implements StanzaHandlerExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StatelessFileSharingUrlSource? getFirstUrlSource() {
|
StatelessFileSharingUrlSource? getFirstUrlSource() {
|
||||||
return firstWhereOrNull(
|
return sources.firstWhereOrNull(
|
||||||
sources,
|
|
||||||
(StatelessFileSharingSource source) =>
|
(StatelessFileSharingSource source) =>
|
||||||
source is StatelessFileSharingUrlSource,
|
source is StatelessFileSharingUrlSource,
|
||||||
) as StatelessFileSharingUrlSource?;
|
) as StatelessFileSharingUrlSource?;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.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_0300.dart';
|
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
@ -54,8 +54,7 @@ class StatelessFileSharingEncryptedSource extends StatelessFileSharingSource {
|
|||||||
final sources = element.firstTag('sources', xmlns: sfsXmlns)!.children;
|
final sources = element.firstTag('sources', xmlns: sfsXmlns)!.children;
|
||||||
|
|
||||||
// Find the first URL source
|
// Find the first URL source
|
||||||
final source = firstWhereOrNull(
|
final source = sources.firstWhereOrNull(
|
||||||
sources,
|
|
||||||
(XMLNode child) =>
|
(XMLNode child) =>
|
||||||
child.tag == 'url-data' && child.attributes['xmlns'] == urlDataXmlns,
|
child.tag == 'url-data' && child.attributes['xmlns'] == urlDataXmlns,
|
||||||
)!;
|
)!;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
@ -9,7 +10,6 @@ import 'package:moxxmpp/src/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_4790.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/util/typed_map.dart';
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
|
@ -10,18 +10,16 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
freezed: ^2.1.0+1
|
|
||||||
freezed_annotation: ^2.1.0
|
|
||||||
hex: ^0.2.0
|
hex: ^0.2.0
|
||||||
json_serializable: ^6.3.1
|
json_serializable: ^6.3.1
|
||||||
logging: ^1.0.2
|
logging: ^1.0.2
|
||||||
meta: ^1.7.0
|
meta: ^1.7.0
|
||||||
moxlib:
|
moxlib:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
version: ^0.1.5
|
version: ^0.2.0
|
||||||
omemo_dart:
|
omemo_dart:
|
||||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||||
version: ^0.4.3
|
version: ^0.5.1
|
||||||
random_string: ^2.3.1
|
random_string: ^2.3.1
|
||||||
saslprep: ^1.0.2
|
saslprep: ^1.0.2
|
||||||
synchronized: ^3.0.0+2
|
synchronized: ^3.0.0+2
|
||||||
@ -32,3 +30,10 @@ dev_dependencies:
|
|||||||
build_runner: ^2.1.11
|
build_runner: ^2.1.11
|
||||||
test: ^1.16.0
|
test: ^1.16.0
|
||||||
very_good_analysis: ^3.0.1
|
very_good_analysis: ^3.0.1
|
||||||
|
|
||||||
|
# TODO: Remove once we release 0.5.1
|
||||||
|
dependency_overrides:
|
||||||
|
omemo_dart:
|
||||||
|
git:
|
||||||
|
url: https://github.com/PapaTutuWawa/omemo_dart.git
|
||||||
|
rev: 49c7e114e6cf80dcde55fbbd218bba3182045862
|
||||||
|
@ -30,7 +30,6 @@ void main() {
|
|||||||
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
expect(queue.queue.length, 2);
|
expect(queue.queue.length, 2);
|
||||||
expect(queue.isRunning, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test sending', () async {
|
test('Test sending', () async {
|
||||||
@ -58,7 +57,6 @@ void main() {
|
|||||||
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
expect(queue.queue.length, 0);
|
expect(queue.queue.length, 0);
|
||||||
expect(queue.isRunning, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test partial sending and resuming', () async {
|
test('Test partial sending and resuming', () async {
|
||||||
@ -89,12 +87,10 @@ void main() {
|
|||||||
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
expect(queue.queue.length, 1);
|
expect(queue.queue.length, 1);
|
||||||
expect(queue.isRunning, false);
|
|
||||||
|
|
||||||
canRun = true;
|
canRun = true;
|
||||||
await queue.restart();
|
await queue.restart();
|
||||||
await Future<void>.delayed(const Duration(seconds: 1));
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
expect(queue.queue.length, 0);
|
expect(queue.queue.length, 0);
|
||||||
expect(queue.isRunning, false);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:moxxmpp/src/parser.dart';
|
import 'package:moxxmpp/src/parser.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import 'helpers/logging.dart';
|
import 'helpers/logging.dart';
|
||||||
|
|
||||||
const exampleXmlns1 = 'im:moxxmpp:example1';
|
const exampleXmlns1 = 'im:moxxmpp:example1';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0198/types.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import '../helpers/logging.dart';
|
import '../helpers/logging.dart';
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
@ -42,10 +43,10 @@ Future<void> runOutgoingStanzaHandlers(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XmppManagerAttributes mkAttributes(void Function(Stanza) callback) {
|
XmppManagerAttributes mkAttributes(void Function(StanzaDetails) callback) {
|
||||||
return XmppManagerAttributes(
|
return XmppManagerAttributes(
|
||||||
sendStanza: (StanzaDetails details) async {
|
sendStanza: (StanzaDetails details) async {
|
||||||
callback(details.stanza);
|
callback(details);
|
||||||
|
|
||||||
return Stanza.message();
|
return Stanza.message();
|
||||||
},
|
},
|
||||||
@ -506,7 +507,7 @@ void main() {
|
|||||||
StreamManagementNegotiator()..resource = 'test-resource',
|
StreamManagementNegotiator()..resource = 'test-resource',
|
||||||
]);
|
]);
|
||||||
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
||||||
StreamManagementState(
|
const StreamManagementState(
|
||||||
10,
|
10,
|
||||||
10,
|
10,
|
||||||
streamResumptionId: 'id-1',
|
streamResumptionId: 'id-1',
|
||||||
@ -605,7 +606,7 @@ void main() {
|
|||||||
StreamManagementNegotiator()..resource = 'abc123',
|
StreamManagementNegotiator()..resource = 'abc123',
|
||||||
]);
|
]);
|
||||||
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
||||||
StreamManagementState(
|
const StreamManagementState(
|
||||||
10,
|
10,
|
||||||
10,
|
10,
|
||||||
streamResumptionId: 'id-1',
|
streamResumptionId: 'id-1',
|
||||||
@ -750,7 +751,7 @@ void main() {
|
|||||||
StreamManagementNegotiator()..resource = 'abc123',
|
StreamManagementNegotiator()..resource = 'abc123',
|
||||||
]);
|
]);
|
||||||
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
await conn.getManagerById<StreamManagementManager>(smManager)!.setState(
|
||||||
StreamManagementState(
|
const StreamManagementState(
|
||||||
10,
|
10,
|
||||||
10,
|
10,
|
||||||
streamResumptionId: 'id-1',
|
streamResumptionId: 'id-1',
|
||||||
@ -1069,4 +1070,49 @@ void main() {
|
|||||||
expect(smn.streamEnablementFailed, true);
|
expect(smn.streamEnablementFailed, true);
|
||||||
expect(conn.resource, 'test-resource');
|
expect(conn.resource, 'test-resource');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test resending state changes', () async {
|
||||||
|
final manager = StreamManagementManager();
|
||||||
|
final attributes = mkAttributes((details) async {
|
||||||
|
for (final handler in manager.getOutgoingPostStanzaHandlers()) {
|
||||||
|
await handler.callback(
|
||||||
|
details.stanza,
|
||||||
|
StanzaHandlerData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
stanza,
|
||||||
|
details.postSendExtensions ?? TypedMap(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
manager.register(attributes);
|
||||||
|
|
||||||
|
await manager.onXmppEvent(
|
||||||
|
StreamManagementEnabledEvent(resource: 'hallo'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send a stanza 5 times
|
||||||
|
for (var i = 0; i < 5; i++) {
|
||||||
|
await runOutgoingStanzaHandlers(manager, stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <a h='3'/>
|
||||||
|
await manager.runNonzaHandlers(mkAck(3));
|
||||||
|
//expect(manager.getUnackedStanzas().length, 2);
|
||||||
|
final oldC2s = manager.state.c2s;
|
||||||
|
final oldQueue = Map<int, SMQueueEntry>.from(manager.getUnackedStanzas());
|
||||||
|
|
||||||
|
// Disconnect and reconnect
|
||||||
|
await manager.onXmppEvent(ConnectingEvent());
|
||||||
|
await manager.onXmppEvent(StreamResumedEvent(h: 3));
|
||||||
|
|
||||||
|
expect(manager.state.c2s, oldC2s);
|
||||||
|
expect(manager.getUnackedStanzas(), oldQueue);
|
||||||
|
|
||||||
|
// Now they get acked
|
||||||
|
await manager.runNonzaHandlers(mkAck(5));
|
||||||
|
expect(manager.getUnackedStanzas().length, 0);
|
||||||
|
expect(manager.state.c2s, 5);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../helpers/logging.dart';
|
import '../helpers/logging.dart';
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
|
@ -17,25 +17,15 @@ Future<void> _runTest(String domain) async {
|
|||||||
final connection = XmppConnection(
|
final connection = XmppConnection(
|
||||||
TestingReconnectionPolicy(),
|
TestingReconnectionPolicy(),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
socket,
|
socket,
|
||||||
);
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('testuser@$domain'),
|
||||||
|
password: 'abc123',
|
||||||
|
);
|
||||||
await connection.registerFeatureNegotiators([
|
await connection.registerFeatureNegotiators([
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
|
||||||
DiscoManager([]),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
MessageManager(),
|
|
||||||
PresenceManager(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
connection.setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@$domain'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await connection.connect(
|
final result = await connection.connect(
|
||||||
shouldReconnect: false,
|
shouldReconnect: false,
|
||||||
|
@ -18,17 +18,15 @@ void main() {
|
|||||||
final connection = XmppConnection(
|
final connection = XmppConnection(
|
||||||
TestingSleepReconnectionPolicy(10),
|
TestingSleepReconnectionPolicy(10),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
TCPSocketWrapper(),
|
TCPSocketWrapper(),
|
||||||
);
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
||||||
|
password: 'abc123',
|
||||||
|
);
|
||||||
await connection.registerFeatureNegotiators([
|
await connection.registerFeatureNegotiators([
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
|
||||||
DiscoManager([]),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
MessageManager(),
|
|
||||||
PresenceManager(),
|
|
||||||
]);
|
|
||||||
connection.asBroadcastStream().listen((event) {
|
connection.asBroadcastStream().listen((event) {
|
||||||
if (event is ConnectionStateChangedEvent) {
|
if (event is ConnectionStateChangedEvent) {
|
||||||
if (event.state == XmppConnectionState.error) {
|
if (event.state == XmppConnectionState.error) {
|
||||||
@ -37,14 +35,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await connection.connect(
|
final result = await connection.connect(
|
||||||
shouldReconnect: false,
|
shouldReconnect: false,
|
||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
@ -68,17 +58,15 @@ void main() {
|
|||||||
final connection = XmppConnection(
|
final connection = XmppConnection(
|
||||||
TestingReconnectionPolicy(),
|
TestingReconnectionPolicy(),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
TCPSocketWrapper(),
|
TCPSocketWrapper(),
|
||||||
);
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
||||||
|
password: 'abc123',
|
||||||
|
);
|
||||||
await connection.registerFeatureNegotiators([
|
await connection.registerFeatureNegotiators([
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
]);
|
]);
|
||||||
await connection.registerManagers([
|
|
||||||
DiscoManager([]),
|
|
||||||
RosterManager(TestingRosterStateManager('', [])),
|
|
||||||
MessageManager(),
|
|
||||||
PresenceManager(),
|
|
||||||
]);
|
|
||||||
connection.asBroadcastStream().listen((event) {
|
connection.asBroadcastStream().listen((event) {
|
||||||
if (event is ConnectionStateChangedEvent) {
|
if (event is ConnectionStateChangedEvent) {
|
||||||
if (event.state == XmppConnectionState.error) {
|
if (event.state == XmppConnectionState.error) {
|
||||||
@ -87,14 +75,6 @@ void main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await connection.connect(
|
final result = await connection.connect(
|
||||||
shouldReconnect: false,
|
shouldReconnect: false,
|
||||||
waitUntilLogin: true,
|
waitUntilLogin: true,
|
||||||
|
@ -12,7 +12,13 @@ dependencies:
|
|||||||
meta: ^1.6.0
|
meta: ^1.6.0
|
||||||
moxxmpp:
|
moxxmpp:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
version: ^0.3.0
|
version: ^0.4.0
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
moxxmpp:
|
||||||
|
git:
|
||||||
|
url: https://codeberg.org/moxxy/moxxmpp.git
|
||||||
|
rev: 05e3d804a4036e9cd93fd27473a1e970fda3c3fc
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
lints: ^2.0.0
|
||||||
|
Loading…
Reference in New Issue
Block a user