Compare commits
115 Commits
327f695a40
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c61ddeb338 | |||
| e2515e25e4 | |||
| 09a849c6eb | |||
| 9eb94e5f48 | |||
| db77790bf4 | |||
| 7ceee48d31 | |||
| 941c3e4fd8 | |||
| 365ff2f238 | |||
| b3c8a6cd2f | |||
| d4166d087e | |||
| ddf781daff | |||
| 5973076b89 | |||
| 72cb76d1f6 | |||
| be7581e841 | |||
| 8a2435e4ad | |||
| 97f082b6f5 | |||
| f287d501ab | |||
| 93e9d6ca22 | |||
| 007cdce53d | |||
| 6d3a5e98de | |||
| e97d6e6517 | |||
| 882d20dc7a | |||
| 1f712151e4 | |||
| e7922668b1 | |||
| 87866bf3f5 | |||
| 41b789fa28 | |||
| 0a68f09fb4 | |||
| edf1d0b257 | |||
| 59b90307c2 | |||
| 49d3c6411b | |||
| 3a94dd9634 | |||
| fb4b4c71e2 | |||
| d9fbb9e102 | |||
| aba90f2e90 | |||
| 9211963390 | |||
| c7d58c3d3f | |||
| 6dbbf08be4 | |||
| 7ca648c478 | |||
| 814f99436b | |||
| 5bd2466c54 | |||
| 14b62cef96 | |||
| c3088f9046 | |||
| 64b93b536e | |||
| c1c48d0a83 | |||
| 4a681b9483 | |||
| c504afc944 | |||
| 76a9f7be7a | |||
| afa3927720 | |||
| 5f36289f50 | |||
| fbe3b90200 | |||
| d7c13abde6 | |||
| d4416c8a47 | |||
| 9666557655 | |||
| 1625f912b0 | |||
| 864cc0e747 | |||
| c9e817054d | |||
| d57bf2ef80 | |||
| 8bfdd5e54a | |||
| e58082bf38 | |||
| dbb945b424 | |||
| 2431eafa6c | |||
| 264ab130ee | |||
| 38dba0e6b7 | |||
| 94d6fe4925 | |||
| c8b903e5df | |||
| b14363319a | |||
| a18507cc3a | |||
| 93418f0127 | |||
| 1e7279e23b | |||
|
|
b2724aba0c | ||
|
|
d3742ea156 | ||
| b92e825bc1 | |||
|
|
8b00e85167 | ||
|
|
04dfc6d2ac | ||
|
|
9e70e802ef | ||
|
|
3ebd9b86ec | ||
| a928c5c877 | |||
| 77a1acb0e7 | |||
|
|
a873edb9ec | ||
|
|
e6bd6d05cd | ||
| 05e3d804a4 | |||
| b5efc2dfae | |||
|
|
b7d53b8f47 | ||
|
|
217c3ac236 | ||
| d35b955259 | |||
| 30dca67fb6 | |||
| 2db44e2f51 | |||
|
|
51bca6c25d | ||
| 4f9a0605c7 | |||
| 3621f2709a | |||
| 9da6d319a3 | |||
| e3ca83670a | |||
| fbbe413148 | |||
|
|
8728166a4d | ||
|
|
1f1321b269 | ||
| 9fd2daabb2 | |||
| 8252472fae | |||
| 3cb5a568ce | |||
| c2f62e2967 | |||
|
|
66195f66fa | ||
|
|
70fdfaf16d | ||
|
|
cd73f89e63 | ||
|
|
05c41d3185 | ||
|
|
64a8de6caa | ||
|
|
68809469f6 | ||
|
|
762cf1c77a | ||
| 29a5417b31 | |||
|
|
255d0f88e0 | ||
| fa2ce7c2d1 | |||
|
|
fa11a3a384 | ||
|
|
ac5bb9e461 | ||
| aa71d3ed5d | |||
| f2d8c6a009 | |||
| 88545e3308 | |||
| 925a46c0da |
2
.gitlint
2
.gitlint
@@ -7,7 +7,7 @@ line-length=72
|
|||||||
[title-trailing-punctuation]
|
[title-trailing-punctuation]
|
||||||
[title-hard-tab]
|
[title-hard-tab]
|
||||||
[title-match-regex]
|
[title-match-regex]
|
||||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all)+(,(meta|tests|style|docs|xep|core|example|all))*\)|release): [A-Z0-9].*$
|
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all|flake|ci)+(,(meta|tests|style|docs|xep|core|example|all|flake|ci))*\)|release): [A-Z0-9].*$
|
||||||
|
|
||||||
|
|
||||||
[body-trailing-whitespace]
|
[body-trailing-whitespace]
|
||||||
|
|||||||
@@ -1,28 +1,49 @@
|
|||||||
|
when:
|
||||||
|
branch: master
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
# Check moxxmpp
|
# Check moxxmpp
|
||||||
moxxmpp-lint:
|
moxxmpp-lint:
|
||||||
image: dart:2.18.1
|
image: dart:3.0.7
|
||||||
commands:
|
commands:
|
||||||
- cd packages/moxxmpp
|
- cd packages/moxxmpp
|
||||||
- dart pub get
|
# Proxy requests to pub.dev using pubcached
|
||||||
|
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||||
- dart analyze --fatal-infos --fatal-warnings
|
- dart analyze --fatal-infos --fatal-warnings
|
||||||
|
|
||||||
moxxmpp-test:
|
moxxmpp-test:
|
||||||
image: dart:2.18.1
|
image: dart:3.0.7
|
||||||
commands:
|
commands:
|
||||||
- cd packages/moxxmpp
|
- cd packages/moxxmpp
|
||||||
- dart pub get
|
# Proxy requests to pub.dev using pubcached
|
||||||
|
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||||
- dart test
|
- dart test
|
||||||
|
|
||||||
# Check moxxmpp_socket_tcp
|
# Check moxxmpp_socket_tcp
|
||||||
moxxmpp_socket_tcp-lint:
|
moxxmpp_socket_tcp-lint:
|
||||||
image: dart:2.18.1
|
image: dart:3.0.7
|
||||||
commands:
|
commands:
|
||||||
- cd packages/moxxmpp_socket_tcp
|
- cd packages/moxxmpp_socket_tcp
|
||||||
- dart pub get
|
# Proxy requests to pub.dev using pubcached
|
||||||
|
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||||
- dart analyze --fatal-infos --fatal-warnings
|
- dart analyze --fatal-infos --fatal-warnings
|
||||||
|
|
||||||
# moxxmpp-test:
|
# moxxmpp-test:
|
||||||
# image: dart:2.18.1
|
# image: dart:3.0.7
|
||||||
# commands:
|
# commands:
|
||||||
# - cd packages/moxxmpp
|
# - cd packages/moxxmpp
|
||||||
# - dart pub get
|
# # Proxy requests to pub.dev using pubcached
|
||||||
|
# - PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||||
# - dart test
|
# - dart test
|
||||||
|
|
||||||
|
notify:
|
||||||
|
image: git.polynom.me/papatutuwawa/woodpecker-xmpp
|
||||||
|
settings:
|
||||||
|
xmpp_tls: 1
|
||||||
|
xmpp_is_muc: 1
|
||||||
|
xmpp_recipient: moxxy-build@muc.moxxy.org
|
||||||
|
xmpp_alias: 2Bot
|
||||||
|
secrets: [ xmpp_jid, xmpp_password, xmpp_server ]
|
||||||
|
when:
|
||||||
|
status:
|
||||||
|
- failure
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
|
|||||||
|
|
||||||
This package contains the actual XMPP code that is platform-independent.
|
This package contains the actual XMPP code that is platform-independent.
|
||||||
|
|
||||||
Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
Documentation is available [here](https://docs.moxxy.org/moxxmpp/index.html).
|
||||||
|
|
||||||
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
|
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
|
||||||
|
|
||||||
@@ -15,6 +15,10 @@ Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
|||||||
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
|
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
|
||||||
if a DNS implementation is given, and supports StartTLS.
|
if a DNS implementation is given, and supports StartTLS.
|
||||||
|
|
||||||
|
### moxxmpp_color
|
||||||
|
|
||||||
|
Implementation of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
To begin, use [melos](https://github.com/invertase/melos) to bootstrap the project: `melos bootstrap`. Then, the example
|
To begin, use [melos](https://github.com/invertase/melos) to bootstrap the project: `melos bootstrap`. Then, the example
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:moxxmpp/moxxmpp.dart';
|
|||||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||||
|
TestingTCPSocketWrapper() : super(true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool onBadCertificate(dynamic certificate, String domain) {
|
bool onBadCertificate(dynamic certificate, String domain) {
|
||||||
return true;
|
return true;
|
||||||
@@ -30,11 +32,12 @@ 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(
|
||||||
|
StanzaDetails(
|
||||||
Stanza.message(
|
Stanza.message(
|
||||||
to: stanza.from,
|
to: stanza.from,
|
||||||
children: [
|
children: [
|
||||||
@@ -45,9 +48,10 @@ class EchoMessageManager extends XmppManagerBase {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
return state..done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
111
examples_dart/bin/muc_client.dart
Normal file
111
examples_dart/bin/muc_client.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
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('muc', help: 'The MUC to send messages to')
|
||||||
|
..parser.addOption('nick', help: 'The nickname with which to join the MUC');
|
||||||
|
final options = parser.handleArguments(args);
|
||||||
|
if (options == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
final muc = JID.fromString(options['muc']! as String).toBare();
|
||||||
|
final nick = options['nick']! as String;
|
||||||
|
final connection = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
ExampleTCPSocketWrapper(parser.srvRecord),
|
||||||
|
)..connectionSettings = parser.connectionSettings;
|
||||||
|
|
||||||
|
// Register the managers and negotiators
|
||||||
|
await connection.registerManagers([
|
||||||
|
PresenceManager(),
|
||||||
|
DiscoManager([]),
|
||||||
|
PubSubManager(),
|
||||||
|
MessageManager(),
|
||||||
|
StableIdManager(),
|
||||||
|
MUCManager(),
|
||||||
|
]);
|
||||||
|
await connection.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
StartTlsNegotiator(),
|
||||||
|
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 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.');
|
||||||
|
|
||||||
|
// Print received messages.
|
||||||
|
connection
|
||||||
|
.asBroadcastStream()
|
||||||
|
.where((event) => event is MessageEvent)
|
||||||
|
.listen((event) {
|
||||||
|
event as MessageEvent;
|
||||||
|
|
||||||
|
// Ignore messages with no <body />
|
||||||
|
final body = event.get<MessageBodyData>()?.body;
|
||||||
|
if (body == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print('=====> [${event.from}] $body');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Join room
|
||||||
|
final mm = connection.getManagerById<MUCManager>(mucManager)!;
|
||||||
|
await mm.joinRoom(
|
||||||
|
muc,
|
||||||
|
nick,
|
||||||
|
maxHistoryStanzas: 0,
|
||||||
|
);
|
||||||
|
final state = (await mm.getRoomState(muc))!;
|
||||||
|
|
||||||
|
print('=====> ${state.members.length} users in room');
|
||||||
|
print('=====> ${state.members.values.map((m) => m.nick).join(", ")}');
|
||||||
|
|
||||||
|
final repl = Repl(prompt: '> ');
|
||||||
|
await for (final line in repl.runAsync()) {
|
||||||
|
await connection
|
||||||
|
.getManagerById<MessageManager>(messageManager)!
|
||||||
|
.sendMessage(
|
||||||
|
muc,
|
||||||
|
TypedMap<StanzaHandlerExtension>.fromList([
|
||||||
|
MessageBodyData(line),
|
||||||
|
StableIdData(
|
||||||
|
// NOTE: Don't do this. Use a UUID.
|
||||||
|
DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
type: 'groupchat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave room
|
||||||
|
await connection.getManagerById<MUCManager>(mucManager)!.leaveRoom(muc);
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
await connection.disconnect();
|
||||||
|
}
|
||||||
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, true),
|
||||||
|
)..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();
|
||||||
|
}
|
||||||
112
examples_dart/bin/simple_client.dart
Normal file
112
examples_dart/bin/simple_client.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
|
/// The JID we want to authenticate as.
|
||||||
|
final xmppUser = JID.fromString('jane@example.com');
|
||||||
|
|
||||||
|
/// The password to authenticate with.
|
||||||
|
const xmppPass = 'secret';
|
||||||
|
|
||||||
|
/// The [xmppHost]:[xmppPort] server address to connect to.
|
||||||
|
/// In a real application, one might prefer to use [TCPSocketWrapper]
|
||||||
|
/// with a custom DNS implementation to let moxxmpp resolve the XMPP
|
||||||
|
/// server's address automatically. However, if we just provide a host
|
||||||
|
/// and a port, then [TCPSocketWrapper] will just skip the resolution and
|
||||||
|
/// immediately use the provided connection details.
|
||||||
|
const xmppHost = 'localhost';
|
||||||
|
const xmppPort = 5222;
|
||||||
|
|
||||||
|
void main(List<String> args) async {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
print('${record.level.name}|${record.time}: ${record.message}');
|
||||||
|
});
|
||||||
|
|
||||||
|
// This class manages every aspect of handling the XMPP stream.
|
||||||
|
final connection = XmppConnection(
|
||||||
|
// A reconnection policy tells the connection how to handle an error
|
||||||
|
// while or after connecting to the server. The [TestingReconnectionPolicy]
|
||||||
|
// immediately triggers a reconnection. In a real implementation, one might
|
||||||
|
// prefer to use a smarter strategy, like using an exponential backoff.
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
|
||||||
|
// A connectivity manager tells the connection when it can connect. This is to
|
||||||
|
// ensure that we're not constantly trying to reconnect because we have no
|
||||||
|
// Internet connection. [AlwaysConnectedConnectivityManager] always says that
|
||||||
|
// we're connected. In a real application, one might prefer to use a smarter
|
||||||
|
// strategy, like using connectivity_plus to query the system's network connectivity
|
||||||
|
// state.
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
|
||||||
|
// This kind of negotiator tells the connection how to handle the stream
|
||||||
|
// negotiations. The [ClientToServerNegotiator] allows to connect to the server
|
||||||
|
// as a regular client. Another negotiator would be the [ComponentToServerNegotiator] that
|
||||||
|
// allows for connections to the server where we're acting as a component.
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
|
||||||
|
// A wrapper around any kind of connection. In this case, we use the [TCPSocketWrapper], which
|
||||||
|
// uses a dart:io Socket/SecureSocket to connect to the server. If you want, you can also
|
||||||
|
// provide your own socket to use, for example, WebSockets or any other connection
|
||||||
|
// mechanism.
|
||||||
|
TCPSocketWrapper(false),
|
||||||
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: xmppUser,
|
||||||
|
password: xmppPass,
|
||||||
|
host: xmppHost,
|
||||||
|
port: xmppPort,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register a set of "managers" that provide you with implementations of various
|
||||||
|
// XEPs. Some have interdependencies, which need to be met. However, this example keeps
|
||||||
|
// it simple and just registers a [MessageManager], which has no required dependencies.
|
||||||
|
await connection.registerManagers([
|
||||||
|
// The [MessageManager] handles receiving and sending <message /> stanzas.
|
||||||
|
MessageManager(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Feature negotiators are objects that tell the connection negotiator what stream features
|
||||||
|
// we can negotiate and enable. moxxmpp negotiators always try to enable their features.
|
||||||
|
await connection.registerFeatureNegotiators([
|
||||||
|
// This negotiator authenticates to the server using SASL PLAIN with the provided
|
||||||
|
// credentials.
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
// This negotiator attempts to bind a resource. By default, it's always a random one.
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
// This negotiator attempts to do StartTLS before authenticating.
|
||||||
|
StartTlsNegotiator(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set up a stream handler for the connection's event stream. Managers and negotiators
|
||||||
|
// may trigger certain events. The [MessageManager], for example, triggers a [MessageEvent]
|
||||||
|
// whenever a message is received. If other managers are registered that parse a message's
|
||||||
|
// contents, then they can add their data to the event.
|
||||||
|
connection.asBroadcastStream().listen((event) {
|
||||||
|
if (event is! MessageEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The text body (contents of the <body /> element) are returned as a
|
||||||
|
// [MessageBodyData] object. However, a message does not have to contain a
|
||||||
|
// body, so it is nullable.
|
||||||
|
final body = event.extensions.get<MessageBodyData>()?.body;
|
||||||
|
print('[<-- ${event.from}] $body');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to the server.
|
||||||
|
final result = await connection.connect(
|
||||||
|
// This flag indicates that we want to reconnect in case something happens.
|
||||||
|
shouldReconnect: true,
|
||||||
|
// This flag indicates that we want the returned Future to only resolve
|
||||||
|
// once the stream negotiations are done and no negotiator has any feature left
|
||||||
|
// to negotiate.
|
||||||
|
waitUntilLogin: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the connection was successful. [connection.connect] can return a boolean
|
||||||
|
// to indicate success or a [XmppError] in case the connection attempt failed.
|
||||||
|
if (!result.isType<bool>()) {
|
||||||
|
print('Failed to connect to server');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
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, bool logData) : super(logData);
|
||||||
|
|
||||||
|
/// 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,25 +1,34 @@
|
|||||||
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: '>=3.0.0 <4.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
|
||||||
version: 0.3.1
|
version: 0.4.0
|
||||||
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.4.0
|
||||||
|
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
|
||||||
|
|||||||
126
flake.lock
generated
126
flake.lock
generated
@@ -1,12 +1,74 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"android-nixpkgs": {
|
||||||
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678901627,
|
"lastModified": 1727554699,
|
||||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
"narHash": "sha256-puBCNL5PW7Pej+6Srmi2YjEgNeE015NFe33hbkkLqeQ=",
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"repo": "android-nixpkgs",
|
||||||
|
"rev": "bc34ef1c71fe9eafcfb1d637b431fca83d746625",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"repo": "android-nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"android-nixpkgs",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1722113426,
|
||||||
|
"narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1726560853,
|
||||||
|
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1692799911,
|
||||||
|
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -17,27 +79,27 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1676076353,
|
"lastModified": 1727348695,
|
||||||
"narHash": "sha256-mdUtE8Tp40cZETwcq5tCwwLqkJVV1ULJQ5GKRtbshag=",
|
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
|
||||||
"owner": "AtaraxiaSjel",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5deb99bdccbbb97e7562dee4ba8a3ee3021688e6",
|
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "AtaraxiaSjel",
|
"owner": "NixOS",
|
||||||
"ref": "update/flutter",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680273054,
|
"lastModified": 1727586919,
|
||||||
"narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=",
|
"narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3",
|
"rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -49,9 +111,39 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"android-nixpkgs": "android-nixpkgs",
|
||||||
"nixpkgs": "nixpkgs",
|
"flake-utils": "flake-utils_2",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
32
flake.nix
32
flake.nix
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
description = "moxxmpp";
|
description = "moxxmpp";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
outputs = { self, nixpkgs, android-nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config = {
|
config = {
|
||||||
@@ -14,9 +14,7 @@
|
|||||||
allowUnfree = true;
|
allowUnfree = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
unstable = import nixpkgs-unstable {
|
# Everything to make Flutter happy
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
android = pkgs.androidenv.composeAndroidPackages {
|
android = pkgs.androidenv.composeAndroidPackages {
|
||||||
# TODO: Find a way to pin these
|
# TODO: Find a way to pin these
|
||||||
#toolsVersion = "26.1.1";
|
#toolsVersion = "26.1.1";
|
||||||
@@ -33,6 +31,7 @@
|
|||||||
useGoogleAPIs = false;
|
useGoogleAPIs = false;
|
||||||
useGoogleTVAddOns = false;
|
useGoogleTVAddOns = false;
|
||||||
};
|
};
|
||||||
|
lib = pkgs.lib;
|
||||||
pinnedJDK = pkgs.jdk17;
|
pinnedJDK = pkgs.jdk17;
|
||||||
|
|
||||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
@@ -51,7 +50,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
devShell = let
|
devShell = let
|
||||||
prosody-newer-community-modules = unstable.prosody.overrideAttrs (old: {
|
prosody-newer-community-modules = pkgs.prosody.overrideAttrs (old: {
|
||||||
communityModules = pkgs.fetchhg {
|
communityModules = pkgs.fetchhg {
|
||||||
url = "https://hg.prosody.im/prosody-modules";
|
url = "https://hg.prosody.im/prosody-modules";
|
||||||
rev = "e3a3a6c86a9f";
|
rev = "e3a3a6c86a9f";
|
||||||
@@ -103,7 +102,26 @@
|
|||||||
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
||||||
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
|
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
|
||||||
|
|
||||||
|
ANDROID_SDK_ROOT = "${android.androidsdk}/share/android-sdk";
|
||||||
|
ANDROID_HOME = "${android.androidsdk}/share/android-sdk";
|
||||||
JAVA_HOME = pinnedJDK;
|
JAVA_HOME = pinnedJDK;
|
||||||
|
|
||||||
|
# Fix an issue with Flutter using an older version of aapt2, which does not know
|
||||||
|
# an used parameter.
|
||||||
|
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${android.androidsdk}/share/android-sdk/build-tools/34.0.0/aapt2";
|
||||||
|
};
|
||||||
|
|
||||||
|
apps = {
|
||||||
|
regenerateNixPackage = let
|
||||||
|
script = pkgs.writeShellScript "regenerate-nix-package.sh" ''
|
||||||
|
set -e
|
||||||
|
${pythonEnv}/bin/python ./scripts/pubspec2lock.py ./packages/moxxmpp/pubspec.lock ./nix/moxxmpp.lock
|
||||||
|
${pythonEnv}/bin/python ./scripts/lock2nix.py ./nix/moxxmpp.lock ./nix/pubcache.moxxmpp.nix moxxmpp
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
type = "app";
|
||||||
|
program = "${script}";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
4
integration_tests/create_users.sh
Normal file
4
integration_tests/create_users.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
set -ex
|
||||||
|
|
||||||
|
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
|
||||||
|
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123
|
||||||
@@ -3,14 +3,16 @@ description: A sample command-line application.
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.0 <3.0.0'
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
logging: ^1.0.2
|
logging: ^1.3.0
|
||||||
moxxmpp: 0.3.0
|
moxxmpp:
|
||||||
moxxmpp_socket_tcp: 0.3.0
|
path: ../packages/moxxmpp
|
||||||
|
moxxmpp_socket_tcp:
|
||||||
|
path: ../packages/moxxmpp_socket_tcp
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^2.0.0
|
build_runner: ^2.4.13
|
||||||
test: ^1.16.0
|
test: ^1.25.8
|
||||||
very_good_analysis: ^3.0.1
|
very_good_analysis: ^6.0.0
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
|||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||||
|
TestingTCPSocketWrapper() : super(true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool onBadCertificate(dynamic certificate, String domain) {
|
bool onBadCertificate(dynamic certificate, String domain) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
|||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||||
|
TestingTCPSocketWrapper() : super(true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool onBadCertificate(dynamic certificate, String domain) {
|
bool onBadCertificate(dynamic certificate, String domain) {
|
||||||
return true;
|
return true;
|
||||||
@@ -27,7 +29,7 @@ void main() {
|
|||||||
ClientToServerNegotiator(),
|
ClientToServerNegotiator(),
|
||||||
TestingTCPSocketWrapper(),
|
TestingTCPSocketWrapper(),
|
||||||
)..connectionSettings = ConnectionSettings(
|
)..connectionSettings = ConnectionSettings(
|
||||||
jid: JID.fromString('testuser@localhost'),
|
jid: JID.fromString('testuser1@localhost'),
|
||||||
password: 'abc123',
|
password: 'abc123',
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 5222,
|
port: 5222,
|
||||||
@@ -40,17 +42,18 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
await conn.registerFeatureNegotiators([
|
await conn.registerFeatureNegotiators([
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
|
SaslScramNegotiator(9, '', '', ScramHashType.sha1),
|
||||||
|
SaslScramNegotiator(10, '', '', ScramHashType.sha256),
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
FASTSaslNegotiator(),
|
FASTSaslNegotiator(),
|
||||||
Bind2Negotiator(),
|
Bind2Negotiator(),
|
||||||
StartTlsNegotiator(),
|
StartTlsNegotiator(),
|
||||||
Sasl2Negotiator(
|
Sasl2Negotiator()
|
||||||
userAgent: const UserAgent(
|
..userAgent = const UserAgent(
|
||||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
software: 'moxxmpp',
|
software: 'moxxmpp',
|
||||||
device: "PapaTutuWawa's awesome device",
|
device: "PapaTutuWawa's awesome device",
|
||||||
),
|
),
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final result = await conn.connect(
|
final result = await conn.connect(
|
||||||
|
|||||||
609
nix/moxxmpp.lock
609
nix/moxxmpp.lock
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,30 @@
|
|||||||
|
# GENERATED BY LOCK2NIX.py
|
||||||
|
# DO NOT EDIT BY HAND
|
||||||
{fetchzip, runCommand} : rec {
|
{fetchzip, runCommand} : rec {
|
||||||
_fe_analyzer_shared = fetchzip {
|
_fe_analyzer_shared = fetchzip {
|
||||||
sha256 = "1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5";
|
sha256 = "15fh9ka41dw4qsynv07msq4i243fibprcmafdygw5x88f7m55fq3";
|
||||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/61.0.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
analyzer = fetchzip {
|
analyzer = fetchzip {
|
||||||
sha256 = "0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r";
|
sha256 = "0w604zngxwfx0xqxvhbxrhdh04wgm6ad6a1lbwnyvmk57amv44np";
|
||||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/analyzer/versions/5.13.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
args = fetchzip {
|
args = fetchzip {
|
||||||
sha256 = "0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g";
|
sha256 = "01ps253280c6dbx0vncw4wga4l2qp1zx779qjj2x06xzb3744zbz";
|
||||||
url = "https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/args/versions/2.4.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
async = fetchzip {
|
async = fetchzip {
|
||||||
sha256 = "00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx";
|
sha256 = "0hfgvjajp5c2mw68186hgrk9v5zjhhi149hlhl0fap274p2v1g3q";
|
||||||
url = "https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/async/versions/2.11.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -49,29 +51,29 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
build_daemon = fetchzip {
|
build_daemon = fetchzip {
|
||||||
sha256 = "0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j";
|
sha256 = "1wn7bq846vgdj62bkh9h25l95xdsndv0jdyw52nyr0591l3bpg3h";
|
||||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
build_resolvers = fetchzip {
|
build_resolvers = fetchzip {
|
||||||
sha256 = "0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5";
|
sha256 = "00h9abhrfmnl0xxziyf6p68sxnbv2ww1c4dhgpnz00mzbmamnq5c";
|
||||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.3.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
build_runner = fetchzip {
|
build_runner = fetchzip {
|
||||||
sha256 = "0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6";
|
sha256 = "0b5ha1l6k0gn2swqgqvfy2vl58klf81sxrjnmk0p7rj1wzbqjm7l";
|
||||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
build_runner_core = fetchzip {
|
build_runner_core = fetchzip {
|
||||||
sha256 = "0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z";
|
sha256 = "07r1kfy6ylm4i4xrb24ns8l26h4h1lgcskmnf8wvq2rd5d5hq790";
|
||||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz";
|
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7%2B1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -84,29 +86,29 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
built_value = fetchzip {
|
built_value = fetchzip {
|
||||||
sha256 = "0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i";
|
sha256 = "1y84imf9xqqy3gnd5zz9bcln6mycy7qx35r70b0izm31ismlbzkv";
|
||||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/built_value/versions/8.6.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
checked_yaml = fetchzip {
|
checked_yaml = fetchzip {
|
||||||
sha256 = "1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k";
|
sha256 = "1sn01yrmj0pkijn08g3v45c3zmyvdygk9svigkkzybgicdwlkpqs";
|
||||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
code_builder = fetchzip {
|
code_builder = fetchzip {
|
||||||
sha256 = "1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg";
|
sha256 = "1shgl7mxiyv0hhw326yqj2b9jxi1h74qxmsnxf1d1xc6yz766p9a";
|
||||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/code_builder/versions/4.6.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
collection = fetchzip {
|
collection = fetchzip {
|
||||||
sha256 = "1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545";
|
sha256 = "1mr8j0078c4z9hhckiq8m735rggsazwfprm0w9gisil51vh7j2mk";
|
||||||
url = "https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/collection/versions/1.18.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -119,29 +121,29 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
coverage = fetchzip {
|
coverage = fetchzip {
|
||||||
sha256 = "0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d";
|
sha256 = "1yy9bgkax5b6kk7qa07p452v82fyj4rl1j03fn366ywyvhfrh6lp";
|
||||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
crypto = fetchzip {
|
crypto = fetchzip {
|
||||||
sha256 = "1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds";
|
sha256 = "100ai8qa4p3dyvvd60c4xa9p0gm06yh0d68xgcfm3giraad8xmqj";
|
||||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
cryptography = fetchzip {
|
cryptography = fetchzip {
|
||||||
sha256 = "0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs";
|
sha256 = "1yxn9slqq93ri81fbr2nbsinz0mpk9wk39ny076ja8q31d4i8v3f";
|
||||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz";
|
url = "https://pub.dartlang.org/packages/cryptography/versions/2.5.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
dart_style = fetchzip {
|
dart_style = fetchzip {
|
||||||
sha256 = "01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v";
|
sha256 = "0cjhrb1hs8iw9smmfd0fgnxq3nm0w8sz17l6q6svyz6kif19wk9k";
|
||||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz";
|
url = "https://pub.dartlang.org/packages/dart_style/versions/2.3.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -154,43 +156,29 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
fixnum = fetchzip {
|
fixnum = fetchzip {
|
||||||
sha256 = "1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk";
|
sha256 = "0nqrzj41ys8dpxf1x70r0kfj1avj0f2j2b7498k8kvc0i9c5asz7";
|
||||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.0.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/fixnum/versions/1.1.0.tar.gz";
|
||||||
stripRoot = false;
|
|
||||||
extension = "tar.gz";
|
|
||||||
};
|
|
||||||
|
|
||||||
freezed = fetchzip {
|
|
||||||
sha256 = "1i9s4djf4vlz56zqn8brcck3n7sk07qay23wmaan991cqydd10iq";
|
|
||||||
url = "https://pub.dartlang.org/packages/freezed/versions/2.1.1.tar.gz";
|
|
||||||
stripRoot = false;
|
|
||||||
extension = "tar.gz";
|
|
||||||
};
|
|
||||||
|
|
||||||
freezed_annotation = fetchzip {
|
|
||||||
sha256 = "0ym120dh1lpfnb68gxh1finm8p9l445q5x10aw8269y469b9k9z3";
|
|
||||||
url = "https://pub.dartlang.org/packages/freezed_annotation/versions/2.1.0.tar.gz";
|
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
frontend_server_client = fetchzip {
|
frontend_server_client = fetchzip {
|
||||||
sha256 = "0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj";
|
sha256 = "096v7ycix5hgnk750s1qgykyghl2mymhdkg39jrlk3kbj6xygq5b";
|
||||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.2.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
glob = fetchzip {
|
glob = fetchzip {
|
||||||
sha256 = "0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs";
|
sha256 = "0ffab3azx8zkma36mk6wnig8bn8g5g0vjrq2gl21y77rxgw9iqxj";
|
||||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/glob/versions/2.1.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
graphs = fetchzip {
|
graphs = fetchzip {
|
||||||
sha256 = "0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9";
|
sha256 = "0fda0j8y6sq1rc9zpzglrzysl5h49y2ji1wq2lq0wx2c609dxm7f";
|
||||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/graphs/versions/2.3.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -217,78 +205,78 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
io = fetchzip {
|
io = fetchzip {
|
||||||
sha256 = "1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni";
|
sha256 = "101kd0rw26vglmr1m5p130kbrp3k7dk4p5nr77wsbwgg53w8c0d4";
|
||||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz";
|
url = "https://pub.dartlang.org/packages/io/versions/1.0.4.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
js = fetchzip {
|
js = fetchzip {
|
||||||
sha256 = "13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50";
|
sha256 = "124a9yqrjdw3p4nnirab9hm9ziwraldlw4q5cb3sr0dcrli74qpw";
|
||||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz";
|
url = "https://pub.dartlang.org/packages/js/versions/0.6.7.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
json_annotation = fetchzip {
|
json_annotation = fetchzip {
|
||||||
sha256 = "1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby";
|
sha256 = "1jjw7p8qyqajgdq4jqvxipq5w0qrq9dpi1qmia70pk995akryh6m";
|
||||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.8.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
json_serializable = fetchzip {
|
json_serializable = fetchzip {
|
||||||
sha256 = "04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg";
|
sha256 = "1pmidql9x6s2pbhdx9x20pwqwvwpfkvrz0h0cm1f8pqis76c90hb";
|
||||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz";
|
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.6.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
logging = fetchzip {
|
logging = fetchzip {
|
||||||
sha256 = "0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs";
|
sha256 = "124hfjs66r30p92ndfmy5fymgy66yk9in97h8sq6fi7r78pqyc7g";
|
||||||
url = "https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/logging/versions/1.2.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
matcher = fetchzip {
|
matcher = fetchzip {
|
||||||
sha256 = "0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0";
|
sha256 = "0inznqkrxqnq09lcbwvda3xd07qfm1k3aa6dv1wy39gvci8hybss";
|
||||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz";
|
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.16.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
meta = fetchzip {
|
meta = fetchzip {
|
||||||
sha256 = "01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5";
|
sha256 = "1l3zaz6q2s9mnm7s674xshsfqspy79p5kdbbnc99rf2l76avv4h3";
|
||||||
url = "https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/meta/versions/1.9.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
mime = fetchzip {
|
mime = fetchzip {
|
||||||
sha256 = "1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896";
|
sha256 = "1dha9z64bsz8xhi0p62vmlyikr8xwbdlrw90hxghmm3rdgd9h25w";
|
||||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/mime/versions/1.0.4.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
moxlib = fetchzip {
|
moxlib = fetchzip {
|
||||||
sha256 = "1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq";
|
sha256 = "1qaacmcqhq33grn2nq8sn23ki62dcmw0fqy589xm1zv6w0pzfmsk";
|
||||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz";
|
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.2.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
node_preamble = fetchzip {
|
node_preamble = fetchzip {
|
||||||
sha256 = "0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx";
|
sha256 = "12ajg76r9aqmqkavvlxbnb3sszg1szcq3f30badkd0xc25mnhyh8";
|
||||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
omemo_dart = fetchzip {
|
omemo_dart = fetchzip {
|
||||||
sha256 = "09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi";
|
sha256 = "0fhf89ic5mdyld25l6rfb37a1fk1f0f2b4d72xi4r7pvr0ddjhz8";
|
||||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz";
|
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.5.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -301,8 +289,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
path = fetchzip {
|
path = fetchzip {
|
||||||
sha256 = "16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181";
|
sha256 = "1mjdhq2fsz6i9krhp2mnaks2bcw34sa4p7mg0v6njk8dgx2754iv";
|
||||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/path/versions/1.8.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -315,8 +303,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
petitparser = fetchzip {
|
petitparser = fetchzip {
|
||||||
sha256 = "1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0";
|
sha256 = "19zqrpb1z77aw1k2s8rsxdfxczzv9934g2rdfj2jyiv3pqgdq8gh";
|
||||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/petitparser/versions/5.4.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -335,16 +323,30 @@
|
|||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protobuf = fetchzip {
|
||||||
|
sha256 = "1jriyisf8bnvq5ygjk93mn2yzdlnii7xrhy6aabz54xr3y4dcy9x";
|
||||||
|
url = "https://pub.dartlang.org/packages/protobuf/versions/2.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
protoc_plugin = fetchzip {
|
||||||
|
sha256 = "0hjjd1xkv4s4g1d5n2aza0kdwlbfl2aivq99230m3yml7irn00jk";
|
||||||
|
url = "https://pub.dartlang.org/packages/protoc_plugin/versions/20.0.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
pub_semver = fetchzip {
|
pub_semver = fetchzip {
|
||||||
sha256 = "1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi";
|
sha256 = "0wpcfz1crxipbjm18m71pl4vl2ra8vw1n93ff8snr54mmlyfb9z1";
|
||||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz";
|
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.4.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
pubspec_parse = fetchzip {
|
pubspec_parse = fetchzip {
|
||||||
sha256 = "19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1";
|
sha256 = "0dj8sf1w61g7vh1ly3sl690z0nwllzjzbapxswmgsglr0ndcyrs1";
|
||||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -364,43 +366,43 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
shelf = fetchzip {
|
shelf = fetchzip {
|
||||||
sha256 = "0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1";
|
sha256 = "10yk98nadrgj5d3r3241kdaywjjs1j10mg8gacv80kg1mhcfdrxp";
|
||||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
shelf_packages_handler = fetchzip {
|
shelf_packages_handler = fetchzip {
|
||||||
sha256 = "199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q";
|
sha256 = "1h8s42nff9ar0xn7yb42m64lpvmqzq8wranqrkkixdnp7w3pmv1x";
|
||||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
shelf_static = fetchzip {
|
shelf_static = fetchzip {
|
||||||
sha256 = "1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34";
|
sha256 = "1bcqynn2z2syrigmrclxgg8hjhd1x9742938i62cicbaga6vclaz";
|
||||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
shelf_web_socket = fetchzip {
|
shelf_web_socket = fetchzip {
|
||||||
sha256 = "0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn";
|
sha256 = "110b5hrqwpnmq16shxxzjmcih5yfs5kh80dn8avfv0xj5iv7n94c";
|
||||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz";
|
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.4.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
source_gen = fetchzip {
|
source_gen = fetchzip {
|
||||||
sha256 = "1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz";
|
sha256 = "1jql5zccv4vnbbvwcpyyvz8l27pg1rviqbp4vrks5313nf4b0kjg";
|
||||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz";
|
url = "https://pub.dartlang.org/packages/source_gen/versions/1.3.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
source_helper = fetchzip {
|
source_helper = fetchzip {
|
||||||
sha256 = "044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd";
|
sha256 = "0mdd02vhcdcv9n58gzbx2q0bphwj0alz312ca1a8xpkf8jx3y8v4";
|
||||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz";
|
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.4.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -413,29 +415,29 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
source_maps = fetchzip {
|
source_maps = fetchzip {
|
||||||
sha256 = "18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6";
|
sha256 = "004lcfka01agxjdw7zjhrffdkisvgx5s61b5gsl8qqk2jd1rswa7";
|
||||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz";
|
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.12.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
source_span = fetchzip {
|
source_span = fetchzip {
|
||||||
sha256 = "1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86";
|
sha256 = "1nybnf7l5chslp4fczhqnrgrhymy844lw7qrj6y08i626dshrd46";
|
||||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/source_span/versions/1.10.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
stack_trace = fetchzip {
|
stack_trace = fetchzip {
|
||||||
sha256 = "0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na";
|
sha256 = "0xpk2cvmgdh46iwip9jsb54fqx13jnina8pk03akxkmsxvag5izb";
|
||||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
stream_channel = fetchzip {
|
stream_channel = fetchzip {
|
||||||
sha256 = "054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32";
|
sha256 = "0nrlw6zcscgnn6818krkbgs9qiv3f7q8pa7ljw1bqkrsb7xabm8s";
|
||||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -455,8 +457,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
synchronized = fetchzip {
|
synchronized = fetchzip {
|
||||||
sha256 = "1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i";
|
sha256 = "1fx1z1p5qkn4qnq24riw5s86vmq645ppg8f74iyv2fc9rvr301ar";
|
||||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz";
|
url = "https://pub.dartlang.org/packages/synchronized/versions/3.1.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -469,36 +471,36 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
test = fetchzip {
|
test = fetchzip {
|
||||||
sha256 = "08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x";
|
sha256 = "002phlj2pg6nll5hv449izxbqk29zwmwc77d0jx2iimz18dgy2r5";
|
||||||
url = "https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/test/versions/1.24.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
test_api = fetchzip {
|
test_api = fetchzip {
|
||||||
sha256 = "0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px";
|
sha256 = "0as1xcywjrd2zax3cm56qmnac12shf8c1ynnzzjwnggm23f61dxb";
|
||||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz";
|
url = "https://pub.dartlang.org/packages/test_api/versions/0.6.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
test_core = fetchzip {
|
test_core = fetchzip {
|
||||||
sha256 = "1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005";
|
sha256 = "1cx2rmz1xzk5z5yh8fpbsrsz4mgjanrw4xvnp0qzdnm2d7vhaq0y";
|
||||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz";
|
url = "https://pub.dartlang.org/packages/test_core/versions/0.5.3.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
timing = fetchzip {
|
timing = fetchzip {
|
||||||
sha256 = "0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg";
|
sha256 = "15jvxsw7v0gwbdlykma60l1qlhlzb3brh6m0sg2bgbfir4l5s9gw";
|
||||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/timing/versions/1.0.1.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
typed_data = fetchzip {
|
typed_data = fetchzip {
|
||||||
sha256 = "1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g";
|
sha256 = "0q6ggc52vfpr8kqaq69h757wy942hvgshhnsr2pjdinb4zk2sxl1";
|
||||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -511,8 +513,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
uuid = fetchzip {
|
uuid = fetchzip {
|
||||||
sha256 = "12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg";
|
sha256 = "1nh1hxfr6bhyadqqcxrpwrphmm75f1iq4rzfjdwa2486xwlh7vx3";
|
||||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz";
|
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.7.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -525,8 +527,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
vm_service = fetchzip {
|
vm_service = fetchzip {
|
||||||
sha256 = "05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5";
|
sha256 = "15ail7rbaq9ksg73cc0mw2k5imbiidl95yfd4v49k81gp5xmj92w";
|
||||||
url = "https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/vm_service/versions/11.10.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -539,8 +541,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
web_socket_channel = fetchzip {
|
web_socket_channel = fetchzip {
|
||||||
sha256 = "147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a";
|
sha256 = "0f9441c4zifb5qadpjg319dcilimpkdhfacnkl543802bf8qjn4w";
|
||||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.4.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
@@ -553,262 +555,262 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
xml = fetchzip {
|
xml = fetchzip {
|
||||||
sha256 = "0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01";
|
sha256 = "120azx71gazvrrn07vd83vrffzrhsqnmf9rdjxl73rra9py8ixiy";
|
||||||
url = "https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz";
|
url = "https://pub.dartlang.org/packages/xml/versions/6.3.0.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
yaml = fetchzip {
|
yaml = fetchzip {
|
||||||
sha256 = "0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw";
|
sha256 = "0awh9dynbhrlq8zgszaiyxbyyn9b6wyps1zww4z2lx62nbma0pda";
|
||||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz";
|
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz";
|
||||||
stripRoot = false;
|
stripRoot = false;
|
||||||
extension = "tar.gz";
|
extension = "tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
pubCache = runCommand "moxxmpp-pub-cache" {} ''
|
pubCache = runCommand "moxxmpp-pub-cache" {} ''
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dartlang.org/_fe_analyzer_shared-50.0.0
|
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dev/_fe_analyzer_shared-61.0.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${analyzer} $out/hosted/pub.dartlang.org/analyzer-5.2.0
|
ln -s ${analyzer} $out/hosted/pub.dev/analyzer-5.13.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${args} $out/hosted/pub.dartlang.org/args-2.3.1
|
ln -s ${args} $out/hosted/pub.dev/args-2.4.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${async} $out/hosted/pub.dartlang.org/async-2.10.0
|
ln -s ${async} $out/hosted/pub.dev/async-2.11.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${boolean_selector} $out/hosted/pub.dartlang.org/boolean_selector-2.1.1
|
ln -s ${boolean_selector} $out/hosted/pub.dev/boolean_selector-2.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build} $out/hosted/pub.dartlang.org/build-2.3.1
|
ln -s ${build} $out/hosted/pub.dev/build-2.3.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build_config} $out/hosted/pub.dartlang.org/build_config-1.1.1
|
ln -s ${build_config} $out/hosted/pub.dev/build_config-1.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build_daemon} $out/hosted/pub.dartlang.org/build_daemon-3.1.0
|
ln -s ${build_daemon} $out/hosted/pub.dev/build_daemon-3.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build_resolvers} $out/hosted/pub.dartlang.org/build_resolvers-2.1.0
|
ln -s ${build_resolvers} $out/hosted/pub.dev/build_resolvers-2.3.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build_runner} $out/hosted/pub.dartlang.org/build_runner-2.3.2
|
ln -s ${build_runner} $out/hosted/pub.dev/build_runner-2.3.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${build_runner_core} $out/hosted/pub.dartlang.org/build_runner_core-7.2.7
|
ln -s ${build_runner_core} $out/hosted/pub.dev/build_runner_core-7.2.7+1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${built_collection} $out/hosted/pub.dartlang.org/built_collection-5.1.1
|
ln -s ${built_collection} $out/hosted/pub.dev/built_collection-5.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${built_value} $out/hosted/pub.dartlang.org/built_value-8.4.2
|
ln -s ${built_value} $out/hosted/pub.dev/built_value-8.6.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${checked_yaml} $out/hosted/pub.dartlang.org/checked_yaml-2.0.1
|
ln -s ${checked_yaml} $out/hosted/pub.dev/checked_yaml-2.0.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${code_builder} $out/hosted/pub.dartlang.org/code_builder-4.3.0
|
ln -s ${code_builder} $out/hosted/pub.dev/code_builder-4.6.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${collection} $out/hosted/pub.dartlang.org/collection-1.17.0
|
ln -s ${collection} $out/hosted/pub.dev/collection-1.18.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${convert} $out/hosted/pub.dartlang.org/convert-3.1.1
|
ln -s ${convert} $out/hosted/pub.dev/convert-3.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${coverage} $out/hosted/pub.dartlang.org/coverage-1.6.1
|
ln -s ${coverage} $out/hosted/pub.dev/coverage-1.6.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${crypto} $out/hosted/pub.dartlang.org/crypto-3.0.2
|
ln -s ${crypto} $out/hosted/pub.dev/crypto-3.0.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${cryptography} $out/hosted/pub.dartlang.org/cryptography-2.0.5
|
ln -s ${cryptography} $out/hosted/pub.dev/cryptography-2.5.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${dart_style} $out/hosted/pub.dartlang.org/dart_style-2.2.4
|
ln -s ${dart_style} $out/hosted/pub.dev/dart_style-2.3.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${file} $out/hosted/pub.dartlang.org/file-6.1.4
|
ln -s ${file} $out/hosted/pub.dev/file-6.1.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${fixnum} $out/hosted/pub.dartlang.org/fixnum-1.0.1
|
ln -s ${fixnum} $out/hosted/pub.dev/fixnum-1.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${freezed} $out/hosted/pub.dartlang.org/freezed-2.1.1
|
ln -s ${frontend_server_client} $out/hosted/pub.dev/frontend_server_client-3.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${freezed_annotation} $out/hosted/pub.dartlang.org/freezed_annotation-2.1.0
|
ln -s ${glob} $out/hosted/pub.dev/glob-2.1.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${frontend_server_client} $out/hosted/pub.dartlang.org/frontend_server_client-3.1.0
|
ln -s ${graphs} $out/hosted/pub.dev/graphs-2.3.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${glob} $out/hosted/pub.dartlang.org/glob-2.1.0
|
ln -s ${hex} $out/hosted/pub.dev/hex-0.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${graphs} $out/hosted/pub.dartlang.org/graphs-2.2.0
|
ln -s ${http_multi_server} $out/hosted/pub.dev/http_multi_server-3.2.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${hex} $out/hosted/pub.dartlang.org/hex-0.2.0
|
ln -s ${http_parser} $out/hosted/pub.dev/http_parser-4.0.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${http_multi_server} $out/hosted/pub.dartlang.org/http_multi_server-3.2.1
|
ln -s ${io} $out/hosted/pub.dev/io-1.0.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${http_parser} $out/hosted/pub.dartlang.org/http_parser-4.0.2
|
ln -s ${js} $out/hosted/pub.dev/js-0.6.7
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${io} $out/hosted/pub.dartlang.org/io-1.0.3
|
ln -s ${json_annotation} $out/hosted/pub.dev/json_annotation-4.8.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${js} $out/hosted/pub.dartlang.org/js-0.6.5
|
ln -s ${json_serializable} $out/hosted/pub.dev/json_serializable-6.6.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${json_annotation} $out/hosted/pub.dartlang.org/json_annotation-4.7.0
|
ln -s ${logging} $out/hosted/pub.dev/logging-1.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${json_serializable} $out/hosted/pub.dartlang.org/json_serializable-6.5.4
|
ln -s ${matcher} $out/hosted/pub.dev/matcher-0.12.16
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${logging} $out/hosted/pub.dartlang.org/logging-1.0.2
|
ln -s ${meta} $out/hosted/pub.dev/meta-1.9.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${matcher} $out/hosted/pub.dartlang.org/matcher-0.12.13
|
ln -s ${mime} $out/hosted/pub.dev/mime-1.0.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
|
||||||
ln -s ${meta} $out/hosted/pub.dartlang.org/meta-1.8.0
|
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
|
||||||
ln -s ${mime} $out/hosted/pub.dartlang.org/mime-1.0.2
|
|
||||||
|
|
||||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47
|
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47
|
||||||
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.1.5
|
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${node_preamble} $out/hosted/pub.dartlang.org/node_preamble-2.0.1
|
ln -s ${node_preamble} $out/hosted/pub.dev/node_preamble-2.0.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47
|
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47
|
||||||
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.4.3
|
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.5.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${package_config} $out/hosted/pub.dartlang.org/package_config-2.1.0
|
ln -s ${package_config} $out/hosted/pub.dev/package_config-2.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${path} $out/hosted/pub.dartlang.org/path-1.8.2
|
ln -s ${path} $out/hosted/pub.dev/path-1.8.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${pedantic} $out/hosted/pub.dartlang.org/pedantic-1.11.1
|
ln -s ${pedantic} $out/hosted/pub.dev/pedantic-1.11.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${petitparser} $out/hosted/pub.dartlang.org/petitparser-5.1.0
|
ln -s ${petitparser} $out/hosted/pub.dev/petitparser-5.4.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${pinenacl} $out/hosted/pub.dartlang.org/pinenacl-0.5.1
|
ln -s ${pinenacl} $out/hosted/pub.dev/pinenacl-0.5.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${pool} $out/hosted/pub.dartlang.org/pool-1.5.1
|
ln -s ${pool} $out/hosted/pub.dev/pool-1.5.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${pub_semver} $out/hosted/pub.dartlang.org/pub_semver-2.1.2
|
ln -s ${protobuf} $out/hosted/pub.dev/protobuf-2.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${pubspec_parse} $out/hosted/pub.dartlang.org/pubspec_parse-1.2.1
|
ln -s ${protoc_plugin} $out/hosted/pub.dev/protoc_plugin-20.0.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${random_string} $out/hosted/pub.dartlang.org/random_string-2.3.1
|
ln -s ${pub_semver} $out/hosted/pub.dev/pub_semver-2.1.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${saslprep} $out/hosted/pub.dartlang.org/saslprep-1.0.2
|
ln -s ${pubspec_parse} $out/hosted/pub.dev/pubspec_parse-1.2.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${shelf} $out/hosted/pub.dartlang.org/shelf-1.4.0
|
ln -s ${random_string} $out/hosted/pub.dev/random_string-2.3.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dartlang.org/shelf_packages_handler-3.0.1
|
ln -s ${saslprep} $out/hosted/pub.dev/saslprep-1.0.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${shelf_static} $out/hosted/pub.dartlang.org/shelf_static-1.1.1
|
ln -s ${shelf} $out/hosted/pub.dev/shelf-1.4.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${shelf_web_socket} $out/hosted/pub.dartlang.org/shelf_web_socket-1.0.3
|
ln -s ${shelf_packages_handler} $out/hosted/pub.dev/shelf_packages_handler-3.0.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${source_gen} $out/hosted/pub.dartlang.org/source_gen-1.2.6
|
ln -s ${shelf_static} $out/hosted/pub.dev/shelf_static-1.1.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${source_helper} $out/hosted/pub.dartlang.org/source_helper-1.3.3
|
ln -s ${shelf_web_socket} $out/hosted/pub.dev/shelf_web_socket-1.0.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dartlang.org/source_map_stack_trace-2.1.1
|
ln -s ${source_gen} $out/hosted/pub.dev/source_gen-1.3.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${source_maps} $out/hosted/pub.dartlang.org/source_maps-0.10.11
|
ln -s ${source_helper} $out/hosted/pub.dev/source_helper-1.3.4
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${source_span} $out/hosted/pub.dartlang.org/source_span-1.9.1
|
ln -s ${source_map_stack_trace} $out/hosted/pub.dev/source_map_stack_trace-2.1.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${stack_trace} $out/hosted/pub.dartlang.org/stack_trace-1.11.0
|
ln -s ${source_maps} $out/hosted/pub.dev/source_maps-0.10.12
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${stream_channel} $out/hosted/pub.dartlang.org/stream_channel-2.1.1
|
ln -s ${source_span} $out/hosted/pub.dev/source_span-1.10.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${stream_transform} $out/hosted/pub.dartlang.org/stream_transform-2.1.0
|
ln -s ${stack_trace} $out/hosted/pub.dev/stack_trace-1.11.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${string_scanner} $out/hosted/pub.dartlang.org/string_scanner-1.2.0
|
ln -s ${stream_channel} $out/hosted/pub.dev/stream_channel-2.1.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${synchronized} $out/hosted/pub.dartlang.org/synchronized-3.0.0+2
|
ln -s ${stream_transform} $out/hosted/pub.dev/stream_transform-2.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${term_glyph} $out/hosted/pub.dartlang.org/term_glyph-1.2.1
|
ln -s ${string_scanner} $out/hosted/pub.dev/string_scanner-1.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${test} $out/hosted/pub.dartlang.org/test-1.22.0
|
ln -s ${synchronized} $out/hosted/pub.dev/synchronized-3.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${test_api} $out/hosted/pub.dartlang.org/test_api-0.4.16
|
ln -s ${term_glyph} $out/hosted/pub.dev/term_glyph-1.2.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${test_core} $out/hosted/pub.dartlang.org/test_core-0.4.20
|
ln -s ${test} $out/hosted/pub.dev/test-1.24.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${timing} $out/hosted/pub.dartlang.org/timing-1.0.0
|
ln -s ${test_api} $out/hosted/pub.dev/test_api-0.6.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${typed_data} $out/hosted/pub.dartlang.org/typed_data-1.3.1
|
ln -s ${test_core} $out/hosted/pub.dev/test_core-0.5.3
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${unorm_dart} $out/hosted/pub.dartlang.org/unorm_dart-0.2.0
|
ln -s ${timing} $out/hosted/pub.dev/timing-1.0.1
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${uuid} $out/hosted/pub.dartlang.org/uuid-3.0.5
|
ln -s ${typed_data} $out/hosted/pub.dev/typed_data-1.3.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${very_good_analysis} $out/hosted/pub.dartlang.org/very_good_analysis-3.1.0
|
ln -s ${unorm_dart} $out/hosted/pub.dev/unorm_dart-0.2.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${vm_service} $out/hosted/pub.dartlang.org/vm_service-9.4.0
|
ln -s ${uuid} $out/hosted/pub.dev/uuid-3.0.7
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${watcher} $out/hosted/pub.dartlang.org/watcher-1.0.2
|
ln -s ${very_good_analysis} $out/hosted/pub.dev/very_good_analysis-3.1.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${web_socket_channel} $out/hosted/pub.dartlang.org/web_socket_channel-2.2.0
|
ln -s ${vm_service} $out/hosted/pub.dev/vm_service-11.10.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dartlang.org/webkit_inspection_protocol-1.2.0
|
ln -s ${watcher} $out/hosted/pub.dev/watcher-1.0.2
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${xml} $out/hosted/pub.dartlang.org/xml-6.2.0
|
ln -s ${web_socket_channel} $out/hosted/pub.dev/web_socket_channel-2.4.0
|
||||||
|
|
||||||
mkdir -p $out/hosted/pub.dartlang.org
|
mkdir -p $out/hosted/pub.dev
|
||||||
ln -s ${yaml} $out/hosted/pub.dartlang.org/yaml-3.1.1
|
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dev/webkit_inspection_protocol-1.2.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dev
|
||||||
|
ln -s ${xml} $out/hosted/pub.dev/xml-6.3.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dev
|
||||||
|
ln -s ${yaml} $out/hosted/pub.dev/yaml-3.1.2
|
||||||
'';
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
## 0.4.1
|
||||||
|
- Moved FAST from staging to xep_0484.dart
|
||||||
|
|
||||||
## 0.4.0
|
## 0.4.0
|
||||||
|
|
||||||
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
|
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
|
||||||
@@ -16,6 +19,16 @@
|
|||||||
- **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.
|
||||||
|
- **BREAKING**: Removed "Extensible File Thumbnails" (The `Thumbnail` type).
|
||||||
|
- *BREAKING*: Rename `UserAvatarManager`'s `getUserAvatar` to `getUserAvatarData`. It now also requires the id of the avatar to fetch
|
||||||
|
- *BREAKING*: `UserAvatarManager`'s `getAvatarId` with `getLatestMetadata`.
|
||||||
|
- The `PubSubManager` now supports PubSub's `max_items` in `getItems`.
|
||||||
|
- *BREAKING*: `vCardManager`'s `VCardAvatarUpdatedEvent` no longer automatically requests the newest VCard avatar.
|
||||||
|
- *BREAKING*: `XmppConnection` now tries to ensure that incoming data is processed in-order. The only exception are awaited stanzas as they are allowed to bypass the queue.
|
||||||
|
- *BREAKING*: If a stanza handler causes an exception, the handler is simply skipped while processing.
|
||||||
|
- Add better logging around what stanza handler is running and if they end processing early.
|
||||||
|
|
||||||
## 0.3.1
|
## 0.3.1
|
||||||
|
|
||||||
|
|||||||
@@ -38,16 +38,17 @@ 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/fast.dart';
|
|
||||||
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0045/events.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0045/xep_0045.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0054.dart';
|
export 'package:moxxmpp/src/xeps/xep_0054.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
export 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0060/helpers.dart';
|
export 'package:moxxmpp/src/xeps/xep_0060/helpers.dart';
|
||||||
@@ -63,6 +64,7 @@ export 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
export 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
export 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0203.dart';
|
export 'package:moxxmpp/src/xeps/xep_0203.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0264.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0280.dart';
|
export 'package:moxxmpp/src/xeps/xep_0280.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0297.dart';
|
export 'package:moxxmpp/src/xeps/xep_0297.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
@@ -85,6 +87,7 @@ export 'package:moxxmpp/src/xeps/xep_0388/errors.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
export 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
export 'package:moxxmpp/src/xeps/xep_0388/user_agent.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
export 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0421.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0446.dart';
|
export 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||||
@@ -92,3 +95,4 @@ export 'package:moxxmpp/src/xeps/xep_0447.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0448.dart';
|
export 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0449.dart';
|
export 'package:moxxmpp/src/xeps/xep_0449.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0461.dart';
|
export 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0484.dart';
|
||||||
|
|||||||
@@ -1,35 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
/// A surrogate key for awaiting stanzas.
|
/// (JID we sent a stanza to, the id of the sent stanza, the tag of the sent stanza).
|
||||||
@immutable
|
// ignore: avoid_private_typedef_functions
|
||||||
class _StanzaSurrogateKey {
|
typedef _StanzaCompositeKey = (String?, String, String);
|
||||||
const _StanzaSurrogateKey(this.sentTo, this.id, this.tag);
|
|
||||||
|
|
||||||
/// The JID the original stanza was sent to. We expect the result to come from the
|
/// Callback function that returns the bare JID of the connection as a String.
|
||||||
/// same JID.
|
typedef GetBareJidCallback = String Function();
|
||||||
final String sentTo;
|
|
||||||
|
|
||||||
/// The ID of the original stanza. We expect the result to have the same ID.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// The tag name of the stanza.
|
|
||||||
final String tag;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => sentTo.hashCode ^ id.hashCode ^ tag.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is _StanzaSurrogateKey &&
|
|
||||||
other.sentTo == sentTo &&
|
|
||||||
other.id == id &&
|
|
||||||
other.tag == tag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This class handles the await semantics for stanzas. Stanzas are given a "unique"
|
/// This class handles the await semantics for stanzas. Stanzas are given a "unique"
|
||||||
/// key equal to the tuple (to, id, tag) with which their response is identified.
|
/// key equal to the tuple (to, id, tag) with which their response is identified.
|
||||||
@@ -40,8 +18,12 @@ class _StanzaSurrogateKey {
|
|||||||
///
|
///
|
||||||
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
|
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
|
||||||
class StanzaAwaiter {
|
class StanzaAwaiter {
|
||||||
|
StanzaAwaiter(this._bareJidCallback);
|
||||||
|
|
||||||
|
final GetBareJidCallback _bareJidCallback;
|
||||||
|
|
||||||
/// The pending stanzas, identified by their surrogate key.
|
/// The pending stanzas, identified by their surrogate key.
|
||||||
final Map<_StanzaSurrogateKey, Completer<XMLNode>> _pending = {};
|
final Map<_StanzaCompositeKey, Completer<XMLNode>> _pending = {};
|
||||||
|
|
||||||
/// The critical section for accessing [StanzaAwaiter._pending].
|
/// The critical section for accessing [StanzaAwaiter._pending].
|
||||||
final Lock _lock = Lock();
|
final Lock _lock = Lock();
|
||||||
@@ -52,30 +34,33 @@ class StanzaAwaiter {
|
|||||||
/// [tag] is the stanza's tag name.
|
/// [tag] is the stanza's tag name.
|
||||||
///
|
///
|
||||||
/// Returns a future that might resolve to the response to the stanza.
|
/// Returns a future that might resolve to the response to the stanza.
|
||||||
Future<Future<XMLNode>> addPending(String to, String id, String tag) async {
|
Future<Future<XMLNode>> addPending(String? to, String id, String tag) async {
|
||||||
|
// Check if we want to send a stanza to our bare JID and replace it with null.
|
||||||
|
final processedTo = to != null && to == _bareJidCallback() ? null : to;
|
||||||
|
|
||||||
final completer = await _lock.synchronized(() {
|
final completer = await _lock.synchronized(() {
|
||||||
final completer = Completer<XMLNode>();
|
final completer = Completer<XMLNode>();
|
||||||
_pending[_StanzaSurrogateKey(to, id, tag)] = completer;
|
_pending[(processedTo, id, tag)] = completer;
|
||||||
return completer;
|
return completer;
|
||||||
});
|
});
|
||||||
|
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the stanza [stanza] is being awaited. [bareJid] is the bare JID of
|
/// Checks if the stanza [stanza] is being awaited.
|
||||||
/// the connection.
|
|
||||||
/// If [stanza] is awaited, resolves the future and returns true. If not, returns
|
/// If [stanza] is awaited, resolves the future and returns true. If not, returns
|
||||||
/// false.
|
/// false.
|
||||||
Future<bool> onData(XMLNode stanza, JID bareJid) async {
|
Future<bool> onData(XMLNode stanza) async {
|
||||||
assert(bareJid.isBare(), 'bareJid must be bare');
|
|
||||||
|
|
||||||
final id = stanza.attributes['id'] as String?;
|
final id = stanza.attributes['id'] as String?;
|
||||||
if (id == null) return false;
|
if (id == null) return false;
|
||||||
|
|
||||||
final key = _StanzaSurrogateKey(
|
// Check if we want to send a stanza to our bare JID and replace it with null.
|
||||||
// Section 8.1.2.1 § 3 of RFC 6120 says that an empty "from" indicates that the
|
final from = stanza.attributes['from'] as String?;
|
||||||
// attribute is implicitly from our own bare JID.
|
final processedFrom =
|
||||||
stanza.attributes['from'] as String? ?? bareJid.toString(),
|
from != null && from == _bareJidCallback() ? null : from;
|
||||||
|
|
||||||
|
final key = (
|
||||||
|
processedFrom,
|
||||||
id,
|
id,
|
||||||
stanza.tag,
|
stanza.tag,
|
||||||
);
|
);
|
||||||
@@ -91,4 +76,19 @@ class StanzaAwaiter {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if [stanza] represents a stanza that is awaited. Returns true, if [stanza]
|
||||||
|
/// is awaited. False, if not.
|
||||||
|
Future<bool> isAwaited(XMLNode stanza) async {
|
||||||
|
final id = stanza.attributes['id'] as String?;
|
||||||
|
if (id == null) return false;
|
||||||
|
|
||||||
|
final key = (
|
||||||
|
stanza.attributes['from'] as String?,
|
||||||
|
id,
|
||||||
|
stanza.tag,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _lock.synchronized(() => _pending.containsKey(key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ 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/incoming_queue.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:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
/// The states the XmppConnection can be in
|
/// The states the XmppConnection can be in
|
||||||
@@ -51,6 +49,19 @@ enum XmppConnectionState {
|
|||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (The actual stanza handler, Name of the owning manager).
|
||||||
|
typedef _StanzaHandlerWrapper = (StanzaHandler, String);
|
||||||
|
|
||||||
|
/// Wrapper around [stanzaHandlerSortComparator] for [_StanzaHandlerWrapper].
|
||||||
|
int _stanzaHandlerWrapperSortComparator(
|
||||||
|
_StanzaHandlerWrapper a,
|
||||||
|
_StanzaHandlerWrapper b,
|
||||||
|
) {
|
||||||
|
final (ha, _) = a;
|
||||||
|
final (hb, _) = b;
|
||||||
|
return stanzaHandlerSortComparator(ha, hb);
|
||||||
|
}
|
||||||
|
|
||||||
/// This class is a connection to the server.
|
/// This class is a connection to the server.
|
||||||
class XmppConnection {
|
class XmppConnection {
|
||||||
XmppConnection(
|
XmppConnection(
|
||||||
@@ -60,7 +71,11 @@ class XmppConnection {
|
|||||||
this._socket, {
|
this._socket, {
|
||||||
this.connectingTimeout = const Duration(minutes: 2),
|
this.connectingTimeout = const Duration(minutes: 2),
|
||||||
}) : _reconnectionPolicy = reconnectionPolicy,
|
}) : _reconnectionPolicy = reconnectionPolicy,
|
||||||
_connectivityManager = connectivityManager {
|
_connectivityManager = connectivityManager,
|
||||||
|
assert(
|
||||||
|
_socket.getDataStream().isBroadcast,
|
||||||
|
"The socket's data stream must be a broadcast stream",
|
||||||
|
) {
|
||||||
// Allow the reconnection policy to perform reconnections by itself
|
// Allow the reconnection policy to perform reconnections by itself
|
||||||
_reconnectionPolicy.register(
|
_reconnectionPolicy.register(
|
||||||
_attemptReconnection,
|
_attemptReconnection,
|
||||||
@@ -79,9 +94,15 @@ class XmppConnection {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_stanzaAwaiter = StanzaAwaiter(
|
||||||
|
() => connectionSettings.jid.toBare().toString(),
|
||||||
|
);
|
||||||
|
_incomingStanzaQueue = IncomingStanzaQueue(handleXmlStream, _stanzaAwaiter);
|
||||||
_socketStream = _socket.getDataStream();
|
_socketStream = _socket.getDataStream();
|
||||||
// TODO(Unknown): Handle on done
|
_socketStream
|
||||||
_socketStream.transform(_streamParser).forEach(handleXmlStream);
|
.transform(_streamParser)
|
||||||
|
.forEach(_incomingStanzaQueue.addStanza);
|
||||||
|
_socketStream.listen(_handleOnDataCallbacks);
|
||||||
_socket.getEventStream().listen(handleSocketEvent);
|
_socket.getEventStream().listen(handleSocketEvent);
|
||||||
|
|
||||||
_stanzaQueue = AsyncStanzaQueue(
|
_stanzaQueue = AsyncStanzaQueue(
|
||||||
@@ -111,16 +132,16 @@ class XmppConnection {
|
|||||||
final ConnectivityManager _connectivityManager;
|
final ConnectivityManager _connectivityManager;
|
||||||
|
|
||||||
/// A helper for handling await semantics with stanzas
|
/// A helper for handling await semantics with stanzas
|
||||||
final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter();
|
late final StanzaAwaiter _stanzaAwaiter;
|
||||||
|
|
||||||
/// Sorted list of handlers that we call or incoming and outgoing stanzas
|
/// Sorted list of handlers that we call or incoming and outgoing stanzas
|
||||||
final List<StanzaHandler> _incomingStanzaHandlers =
|
final List<_StanzaHandlerWrapper> _incomingStanzaHandlers =
|
||||||
List.empty(growable: true);
|
List.empty(growable: true);
|
||||||
final List<StanzaHandler> _incomingPreStanzaHandlers =
|
final List<_StanzaHandlerWrapper> _incomingPreStanzaHandlers =
|
||||||
List.empty(growable: true);
|
List.empty(growable: true);
|
||||||
final List<StanzaHandler> _outgoingPreStanzaHandlers =
|
final List<_StanzaHandlerWrapper> _outgoingPreStanzaHandlers =
|
||||||
List.empty(growable: true);
|
List.empty(growable: true);
|
||||||
final List<StanzaHandler> _outgoingPostStanzaHandlers =
|
final List<_StanzaHandlerWrapper> _outgoingPostStanzaHandlers =
|
||||||
List.empty(growable: true);
|
List.empty(growable: true);
|
||||||
final StreamController<XmppEvent> _eventStreamController =
|
final StreamController<XmppEvent> _eventStreamController =
|
||||||
StreamController.broadcast();
|
StreamController.broadcast();
|
||||||
@@ -159,10 +180,6 @@ class XmppConnection {
|
|||||||
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
|
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
|
||||||
_negotiationsHandler.getNegotiatorById<T>(id);
|
_negotiationsHandler.getNegotiatorById<T>(id);
|
||||||
|
|
||||||
/// Prevent data from being passed to _currentNegotiator.negotiator while the negotiator
|
|
||||||
/// is still running.
|
|
||||||
final Lock _negotiationLock = Lock();
|
|
||||||
|
|
||||||
/// The logger for the class
|
/// The logger for the class
|
||||||
final Logger _log = Logger('XmppConnection');
|
final Logger _log = Logger('XmppConnection');
|
||||||
|
|
||||||
@@ -171,6 +188,8 @@ class XmppConnection {
|
|||||||
|
|
||||||
bool get isAuthenticated => _isAuthenticated;
|
bool get isAuthenticated => _isAuthenticated;
|
||||||
|
|
||||||
|
late final IncomingStanzaQueue _incomingStanzaQueue;
|
||||||
|
|
||||||
late final AsyncStanzaQueue _stanzaQueue;
|
late final AsyncStanzaQueue _stanzaQueue;
|
||||||
|
|
||||||
/// Returns the JID we authenticate with and add the resource that we have bound.
|
/// Returns the JID we authenticate with and add the resource that we have bound.
|
||||||
@@ -200,18 +219,25 @@ class XmppConnection {
|
|||||||
|
|
||||||
_xmppManagers[manager.id] = manager;
|
_xmppManagers[manager.id] = manager;
|
||||||
|
|
||||||
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
|
_incomingStanzaHandlers.addAll(
|
||||||
_incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers());
|
manager.getIncomingStanzaHandlers().map((h) => (h, manager.name)),
|
||||||
_outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers());
|
);
|
||||||
_outgoingPostStanzaHandlers
|
_incomingPreStanzaHandlers.addAll(
|
||||||
.addAll(manager.getOutgoingPostStanzaHandlers());
|
manager.getIncomingPreStanzaHandlers().map((h) => (h, manager.name)),
|
||||||
|
);
|
||||||
|
_outgoingPreStanzaHandlers.addAll(
|
||||||
|
manager.getOutgoingPreStanzaHandlers().map((h) => (h, manager.name)),
|
||||||
|
);
|
||||||
|
_outgoingPostStanzaHandlers.addAll(
|
||||||
|
manager.getOutgoingPostStanzaHandlers().map((h) => (h, manager.name)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort them
|
// Sort them
|
||||||
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_incomingStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||||
_incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_incomingPreStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||||
_outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_outgoingPreStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||||
_outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator);
|
_outgoingPostStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||||
|
|
||||||
// Run the post register callbacks
|
// Run the post register callbacks
|
||||||
for (final manager in _xmppManagers.values) {
|
for (final manager in _xmppManagers.values) {
|
||||||
@@ -292,6 +318,13 @@ class XmppConnection {
|
|||||||
return getManagerById(csiManager);
|
return getManagerById(csiManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called whenever we receive data on the socket.
|
||||||
|
Future<void> _handleOnDataCallbacks(String _) async {
|
||||||
|
for (final manager in _xmppManagers.values) {
|
||||||
|
unawaited(manager.onData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempts to reconnect to the server by following an exponential backoff.
|
/// Attempts to reconnect to the server by following an exponential backoff.
|
||||||
Future<void> _attemptReconnection() async {
|
Future<void> _attemptReconnection() async {
|
||||||
_log.finest('_attemptReconnection: Setting state to notConnected');
|
_log.finest('_attemptReconnection: Setting state to notConnected');
|
||||||
@@ -314,7 +347,7 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Called when a stream ending error has occurred
|
/// Called when a stream ending error has occurred
|
||||||
Future<void> handleError(XmppError error) async {
|
Future<void> handleError(XmppError error) async {
|
||||||
_log.severe('handleError called with ${error.toString()}');
|
_log.severe('handleError called with $error');
|
||||||
|
|
||||||
// Whenever we encounter an error that would trigger a reconnection attempt while
|
// Whenever we encounter an error that would trigger a reconnection attempt while
|
||||||
// the connection result is being awaited, don't attempt a reconnection but instead
|
// the connection result is being awaited, don't attempt a reconnection but instead
|
||||||
@@ -477,8 +510,9 @@ class XmppConnection {
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
newStanza,
|
newStanza,
|
||||||
TypedMap(),
|
details.extensions ?? TypedMap(),
|
||||||
encrypted: details.encrypted,
|
encrypted: details.encrypted,
|
||||||
|
shouldEncrypt: details.shouldEncrypt,
|
||||||
forceEncryption: details.forceEncryption,
|
forceEncryption: details.forceEncryption,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -516,7 +550,7 @@ class XmppConnection {
|
|||||||
// A stanza with no to attribute is for direct processing by the server. As such,
|
// A stanza with no to attribute is for direct processing by the server. As such,
|
||||||
// we can correlate it by just *assuming* we have that attribute
|
// we can correlate it by just *assuming* we have that attribute
|
||||||
// (RFC 6120 Section 8.1.1.1)
|
// (RFC 6120 Section 8.1.1.1)
|
||||||
data.stanza.to ?? connectionSettings.jid.toBare().toString(),
|
data.stanza.to,
|
||||||
data.stanza.id!,
|
data.stanza.id!,
|
||||||
data.stanza.tag,
|
data.stanza.tag,
|
||||||
)
|
)
|
||||||
@@ -533,15 +567,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');
|
||||||
@@ -652,15 +685,30 @@ class XmppConnection {
|
|||||||
/// call its callback and end the processing if the callback returned true; continue
|
/// call its callback and end the processing if the callback returned true; continue
|
||||||
/// if it returned false.
|
/// if it returned false.
|
||||||
Future<StanzaHandlerData> _runStanzaHandlers(
|
Future<StanzaHandlerData> _runStanzaHandlers(
|
||||||
List<StanzaHandler> handlers,
|
List<_StanzaHandlerWrapper> handlers,
|
||||||
Stanza stanza, {
|
Stanza stanza, {
|
||||||
StanzaHandlerData? initial,
|
StanzaHandlerData? initial,
|
||||||
}) async {
|
}) async {
|
||||||
var state = initial ?? StanzaHandlerData(false, false, stanza, TypedMap());
|
var state = initial ?? StanzaHandlerData(false, false, stanza, TypedMap());
|
||||||
for (final handler in handlers) {
|
for (final handlerRaw in handlers) {
|
||||||
|
final (handler, managerName) = handlerRaw;
|
||||||
if (handler.matches(state.stanza)) {
|
if (handler.matches(state.stanza)) {
|
||||||
|
_log.finest(
|
||||||
|
'Running handler for ${stanza.tag} (${stanza.attributes["id"]}) of $managerName',
|
||||||
|
);
|
||||||
|
try {
|
||||||
state = await handler.callback(state.stanza, state);
|
state = await handler.callback(state.stanza, state);
|
||||||
if (state.done || state.cancel) return state;
|
} catch (ex) {
|
||||||
|
_log.severe(
|
||||||
|
'Handler from $managerName for ${stanza.tag} (${stanza.attributes["id"]}) failed with "$ex"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (state.done || state.cancel) {
|
||||||
|
_log.finest(
|
||||||
|
'Processing ended early for ${stanza.tag} (${stanza.attributes["id"]}) by $managerName',
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,9 +784,15 @@ 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(),
|
|
||||||
);
|
);
|
||||||
if (awaited) {
|
if (awaited) {
|
||||||
return;
|
return;
|
||||||
@@ -753,6 +807,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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -796,14 +851,12 @@ class XmppConnection {
|
|||||||
// causing (a) the negotiator to become confused and (b) the stanzas/nonzas to be
|
// causing (a) the negotiator to become confused and (b) the stanzas/nonzas to be
|
||||||
// missed. This causes the data to wait while the negotiator is running and thus
|
// missed. This causes the data to wait while the negotiator is running and thus
|
||||||
// prevent this issue.
|
// prevent this issue.
|
||||||
await _negotiationLock.synchronized(() async {
|
|
||||||
if (_routingState != RoutingState.negotiating) {
|
if (_routingState != RoutingState.negotiating) {
|
||||||
unawaited(handleXmlStream(event));
|
unawaited(handleXmlStream(event));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _negotiationsHandler.negotiate(event);
|
await _negotiationsHandler.negotiate(event);
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case RoutingState.handleStanzas:
|
case RoutingState.handleStanzas:
|
||||||
await _handleStanza(node);
|
await _handleStanza(node);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -184,16 +184,12 @@ class UserAvatarUpdatedEvent extends XmppEvent {
|
|||||||
class VCardAvatarUpdatedEvent extends XmppEvent {
|
class VCardAvatarUpdatedEvent extends XmppEvent {
|
||||||
VCardAvatarUpdatedEvent(
|
VCardAvatarUpdatedEvent(
|
||||||
this.jid,
|
this.jid,
|
||||||
this.base64,
|
|
||||||
this.hash,
|
this.hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The JID of the entity that updated their avatar.
|
/// The JID of the entity that updated their avatar.
|
||||||
final JID jid;
|
final JID jid;
|
||||||
|
|
||||||
/// The base64-encoded avatar data.
|
|
||||||
final String base64;
|
|
||||||
|
|
||||||
/// The SHA-1 hash of the avatar.
|
/// The SHA-1 hash of the avatar.
|
||||||
final String hash;
|
final String hash;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,6 @@ class JID {
|
|||||||
|
|
||||||
/// Converts the JID into a bare JID.
|
/// Converts the JID into a bare JID.
|
||||||
JID toBare() {
|
JID toBare() {
|
||||||
if (isBare()) return this;
|
|
||||||
|
|
||||||
return JID(local, domain, '');
|
return JID(local, domain, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -81,6 +80,9 @@ abstract class XmppManagerBase {
|
|||||||
/// handler's priority, the earlier it is run.
|
/// handler's priority, the earlier it is run.
|
||||||
List<NonzaHandler> getNonzaHandlers() => [];
|
List<NonzaHandler> getNonzaHandlers() => [];
|
||||||
|
|
||||||
|
/// Whenever the socket receives data, this method is called, if it is non-null.
|
||||||
|
Future<void> onData() async {}
|
||||||
|
|
||||||
/// Return a list of features that should be included in a disco response.
|
/// Return a list of features that should be included in a disco response.
|
||||||
List<String> getDiscoFeatures() => [];
|
List<String> getDiscoFeatures() => [];
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -32,3 +32,5 @@ const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
|||||||
const stickersManager = 'org.moxxmpp.stickersmanager';
|
const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||||
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||||
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
|
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
|
||||||
|
const occupantIdManager = 'org.moxxmpp.occupantidmanager';
|
||||||
|
const mucManager = 'org.moxxmpp.mucmanager';
|
||||||
|
|||||||
@@ -66,23 +66,30 @@ class MessageManager extends XmppManagerBase {
|
|||||||
stanzaTag: 'message',
|
stanzaTag: 'message',
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
priority: messageHandlerPriority,
|
priority: messageHandlerPriority,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
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,
|
||||||
@@ -96,19 +103,21 @@ class MessageManager extends XmppManagerBase {
|
|||||||
/// data for building the message.
|
/// data for building the message.
|
||||||
Future<void> sendMessage(
|
Future<void> sendMessage(
|
||||||
JID to,
|
JID to,
|
||||||
TypedMap<StanzaHandlerExtension> extensions,
|
TypedMap<StanzaHandlerExtension> extensions, {
|
||||||
) async {
|
String type = 'chat',
|
||||||
|
}) async {
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.message(
|
Stanza.message(
|
||||||
to: to.toString(),
|
to: to.toString(),
|
||||||
id: extensions.get<MessageIdData>()?.id,
|
id: extensions.get<MessageIdData>()?.id,
|
||||||
type: 'chat',
|
type: type,
|
||||||
children: _messageSendingCallbacks
|
children: _messageSendingCallbacks
|
||||||
.map((c) => c(extensions))
|
.map((c) => c(extensions))
|
||||||
.flattened
|
.flattened
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
extensions: extensions,
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
|
|||||||
|
|
||||||
// XEP-0004
|
// XEP-0004
|
||||||
const dataFormsXmlns = 'jabber:x:data';
|
const dataFormsXmlns = 'jabber:x:data';
|
||||||
|
const formVarFormType = 'FORM_TYPE';
|
||||||
|
|
||||||
// XEP-0030
|
// XEP-0030
|
||||||
const discoInfoXmlns = 'http://jabber.org/protocol/disco#info';
|
const discoInfoXmlns = 'http://jabber.org/protocol/disco#info';
|
||||||
@@ -21,6 +22,11 @@ const discoItemsXmlns = 'http://jabber.org/protocol/disco#items';
|
|||||||
// XEP-0033
|
// XEP-0033
|
||||||
const extendedAddressingXmlns = 'http://jabber.org/protocol/address';
|
const extendedAddressingXmlns = 'http://jabber.org/protocol/address';
|
||||||
|
|
||||||
|
// XEP-0045
|
||||||
|
const mucXmlns = 'http://jabber.org/protocol/muc';
|
||||||
|
const mucUserXmlns = 'http://jabber.org/protocol/muc#user';
|
||||||
|
const roomInfoFormType = 'http://jabber.org/protocol/muc#roominfo';
|
||||||
|
|
||||||
// XEP-0054
|
// XEP-0054
|
||||||
const vCardTempXmlns = 'vcard-temp';
|
const vCardTempXmlns = 'vcard-temp';
|
||||||
const vCardTempUpdate = 'vcard-temp:x:update';
|
const vCardTempUpdate = 'vcard-temp:x:update';
|
||||||
@@ -66,6 +72,9 @@ const delayedDeliveryXmlns = 'urn:xmpp:delay';
|
|||||||
// XEP-0234
|
// XEP-0234
|
||||||
const jingleFileTransferXmlns = 'urn:xmpp:jingle:apps:file-transfer:5';
|
const jingleFileTransferXmlns = 'urn:xmpp:jingle:apps:file-transfer:5';
|
||||||
|
|
||||||
|
// XEP-0264
|
||||||
|
const jingleContentThumbnailXmlns = 'urn:xmpp:thumbs:1';
|
||||||
|
|
||||||
// XEP-0280
|
// XEP-0280
|
||||||
const carbonsXmlns = 'urn:xmpp:carbons:2';
|
const carbonsXmlns = 'urn:xmpp:carbons:2';
|
||||||
|
|
||||||
@@ -123,6 +132,9 @@ const sasl2Xmlns = 'urn:xmpp:sasl:2';
|
|||||||
// XEP-0420
|
// XEP-0420
|
||||||
const sceXmlns = 'urn:xmpp:sce:1';
|
const sceXmlns = 'urn:xmpp:sce:1';
|
||||||
|
|
||||||
|
// XEP-0421
|
||||||
|
const occupantIdXmlns = 'urn:xmpp:occupant-id:0';
|
||||||
|
|
||||||
// XEP-0422
|
// XEP-0422
|
||||||
const fasteningXmlns = 'urn:xmpp:fasten:0';
|
const fasteningXmlns = 'urn:xmpp:fasten:0';
|
||||||
|
|
||||||
@@ -154,7 +166,6 @@ const stickersXmlns = 'urn:xmpp:stickers:0';
|
|||||||
|
|
||||||
// XEP-0461
|
// XEP-0461
|
||||||
const replyXmlns = 'urn:xmpp:reply:0';
|
const replyXmlns = 'urn:xmpp:reply:0';
|
||||||
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
|
||||||
|
|
||||||
// ???
|
// ???
|
||||||
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ class _ChunkedConversionBuffer<S, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A buffer to put between a socket's input and a full XML stream.
|
/// A buffer to put between a socket's input and a full XML stream.
|
||||||
class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
class XMPPStreamParser
|
||||||
final StreamController<XMPPStreamObject> _streamController =
|
extends StreamTransformerBase<String, List<XMPPStreamObject>> {
|
||||||
StreamController<XMPPStreamObject>();
|
final StreamController<List<XMPPStreamObject>> _streamController =
|
||||||
|
StreamController<List<XMPPStreamObject>>();
|
||||||
|
|
||||||
/// Turns a String into a list of [XmlEvent]s in a chunked fashion.
|
/// Turns a String into a list of [XmlEvent]s in a chunked fashion.
|
||||||
_ChunkedConversionBuffer<String, XmlEvent> _eventBuffer =
|
_ChunkedConversionBuffer<String, XmlEvent> _eventBuffer =
|
||||||
@@ -117,13 +118,14 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<XMPPStreamObject> bind(Stream<String> stream) {
|
Stream<List<XMPPStreamObject>> bind(Stream<String> stream) {
|
||||||
// We do not want to use xml's toXmlEvents and toSubtreeEvents methods as they
|
// We do not want to use xml's toXmlEvents and toSubtreeEvents methods as they
|
||||||
// create streams we cannot close. We need to be able to destroy and recreate an
|
// create streams we cannot close. We need to be able to destroy and recreate an
|
||||||
// XML parser whenever we start a new connection.
|
// XML parser whenever we start a new connection.
|
||||||
stream.listen((input) {
|
stream.listen((input) {
|
||||||
final events = _eventBuffer.convert(input);
|
final events = _eventBuffer.convert(input);
|
||||||
final streamHeaderEvents = _streamHeaderSelector.convert(events);
|
final streamHeaderEvents = _streamHeaderSelector.convert(events);
|
||||||
|
final objects = List<XMPPStreamObject>.empty(growable: true);
|
||||||
|
|
||||||
// Process the stream header separately.
|
// Process the stream header separately.
|
||||||
for (final event in streamHeaderEvents) {
|
for (final event in streamHeaderEvents) {
|
||||||
@@ -135,7 +137,7 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_streamController.add(
|
objects.add(
|
||||||
XMPPStreamHeader(
|
XMPPStreamHeader(
|
||||||
Map<String, String>.fromEntries(
|
Map<String, String>.fromEntries(
|
||||||
event.attributes.map((attr) {
|
event.attributes.map((attr) {
|
||||||
@@ -151,13 +153,15 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
|||||||
final children = _childBuffer.convert(childEvents);
|
final children = _childBuffer.convert(childEvents);
|
||||||
for (final node in children) {
|
for (final node in children) {
|
||||||
if (node.nodeType == XmlNodeType.ELEMENT) {
|
if (node.nodeType == XmlNodeType.ELEMENT) {
|
||||||
_streamController.add(
|
objects.add(
|
||||||
XMPPStreamElement(
|
XMPPStreamElement(
|
||||||
XMLNode.fromXmlElement(node as XmlElement),
|
XMLNode.fromXmlElement(node as XmlElement),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_streamController.add(objects);
|
||||||
});
|
});
|
||||||
|
|
||||||
return _streamController.stream;
|
return _streamController.stream;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -64,7 +66,7 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
stanzaTag: 'presence',
|
stanzaTag: 'presence',
|
||||||
callback: _onPresence,
|
callback: _onPresence,
|
||||||
priority: presenceHandlerPriority,
|
priority: presenceHandlerPriority,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -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';
|
||||||
@@ -96,7 +96,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
|
if (pickedForSasl2) {
|
||||||
state = NegotiatorState.done;
|
state = NegotiatorState.done;
|
||||||
|
}
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -246,6 +246,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
bool _checkSignature(String base64Signature) {
|
bool _checkSignature(String base64Signature) {
|
||||||
final signature =
|
final signature =
|
||||||
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
||||||
|
_log.finest(
|
||||||
|
'Expecting signature: "$_serverSignature", got: "${signature["v"]}"',
|
||||||
|
);
|
||||||
return signature['v']! == _serverSignature;
|
return signature['v']! == _serverSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +363,11 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
|
// Don't do anything if we have not been picked for SASL2.
|
||||||
|
if (!pickedForSasl2) {
|
||||||
|
return const Result(true);
|
||||||
|
}
|
||||||
|
|
||||||
// When we're done with SASL2, check the additional data to verify the server
|
// When we're done with SASL2, check the additional data to verify the server
|
||||||
// signature.
|
// signature.
|
||||||
state = NegotiatorState.done;
|
state = NegotiatorState.done;
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -122,7 +122,7 @@ class RosterManager extends XmppManagerBase {
|
|||||||
tagName: 'query',
|
tagName: 'query',
|
||||||
tagXmlns: rosterXmlns,
|
tagXmlns: rosterXmlns,
|
||||||
callback: _onRosterPush,
|
callback: _onRosterPush,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -182,8 +182,18 @@ class RosterManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Shared code between requesting rosters without and with roster versioning, if
|
/// Shared code between requesting rosters without and with roster versioning, if
|
||||||
/// the server deems a regular roster response more efficient than n roster pushes.
|
/// the server deems a regular roster response more efficient than n roster pushes.
|
||||||
|
///
|
||||||
|
/// [query] is the <query /> child of the iq, if available.
|
||||||
|
///
|
||||||
|
/// If roster versioning was used, then [requestedRosterVersion] is the version
|
||||||
|
/// we requested the roster with.
|
||||||
|
///
|
||||||
|
/// Note that if roster versioning is used and the server returns us an empty iq,
|
||||||
|
/// it means that the roster did not change since the last version. In that case,
|
||||||
|
/// we do nothing and just return. The roster state manager will not be notified.
|
||||||
Future<Result<RosterRequestResult, RosterError>> _handleRosterResponse(
|
Future<Result<RosterRequestResult, RosterError>> _handleRosterResponse(
|
||||||
XMLNode? query,
|
XMLNode? query,
|
||||||
|
String? requestedRosterVersion,
|
||||||
) async {
|
) async {
|
||||||
final List<XmppRosterItem> items;
|
final List<XmppRosterItem> items;
|
||||||
String? rosterVersion;
|
String? rosterVersion;
|
||||||
@@ -204,6 +214,14 @@ class RosterManager extends XmppManagerBase {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
rosterVersion = query.attributes['ver'] as String?;
|
rosterVersion = query.attributes['ver'] as String?;
|
||||||
|
} else if (requestedRosterVersion != null) {
|
||||||
|
// Skip the handleRosterFetch call since nothing changed.
|
||||||
|
return Result(
|
||||||
|
RosterRequestResult(
|
||||||
|
[],
|
||||||
|
requestedRosterVersion,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Server response to roster request without roster versioning does not contain a <query /> element, while the type is not error. This violates RFC6121',
|
'Server response to roster request without roster versioning does not contain a <query /> element, while the type is not error. This violates RFC6121',
|
||||||
@@ -258,7 +276,7 @@ class RosterManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final responseQuery = response.firstTag('query', xmlns: rosterXmlns);
|
final responseQuery = response.firstTag('query', xmlns: rosterXmlns);
|
||||||
return _handleRosterResponse(responseQuery);
|
return _handleRosterResponse(responseQuery, rosterVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests a series of roster pushes according to RFC6121. Requires that the server
|
/// Requests a series of roster pushes according to RFC6121. Requires that the server
|
||||||
@@ -266,6 +284,7 @@ class RosterManager extends XmppManagerBase {
|
|||||||
Future<Result<RosterRequestResult?, RosterError>>
|
Future<Result<RosterRequestResult?, RosterError>>
|
||||||
requestRosterPushes() async {
|
requestRosterPushes() async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
|
final rosterVersion = await _stateManager.getRosterVersion();
|
||||||
final result = (await attrs.sendStanza(
|
final result = (await attrs.sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
@@ -275,9 +294,9 @@ class RosterManager extends XmppManagerBase {
|
|||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: rosterXmlns,
|
xmlns: rosterXmlns,
|
||||||
attributes: {
|
attributes: {
|
||||||
'ver': await _stateManager.getRosterVersion() ?? '',
|
'ver': rosterVersion ?? '',
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -289,7 +308,7 @@ class RosterManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final query = result.firstTag('query', xmlns: rosterXmlns);
|
final query = result.firstTag('query', xmlns: rosterXmlns);
|
||||||
return _handleRosterResponse(query);
|
return _handleRosterResponse(query, rosterVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rosterVersioningAvailable() {
|
bool rosterVersioningAvailable() {
|
||||||
@@ -319,14 +338,12 @@ class RosterManager extends XmppManagerBase {
|
|||||||
tag: 'item',
|
tag: 'item',
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
'jid': jid,
|
'jid': jid,
|
||||||
...title == jid.split('@')[0]
|
if (title == jid.split('@')[0]) 'name': title,
|
||||||
? <String, String>{}
|
|
||||||
: <String, String>{'name': title}
|
|
||||||
},
|
},
|
||||||
children: (groups ?? [])
|
children: (groups ?? [])
|
||||||
.map((group) => XMLNode(tag: 'group', text: group))
|
.map((group) => XMLNode(tag: 'group', text: group))
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -357,13 +374,13 @@ class RosterManager extends XmppManagerBase {
|
|||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'item',
|
tag: 'item',
|
||||||
attributes: <String, String>{
|
attributes: {
|
||||||
'jid': jid,
|
'jid': jid,
|
||||||
'subscription': 'remove'
|
'subscription': 'remove',
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,30 +1,44 @@
|
|||||||
|
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 {
|
||||||
const StanzaDetails(
|
const StanzaDetails(
|
||||||
this.stanza, {
|
this.stanza, {
|
||||||
|
this.extensions,
|
||||||
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.
|
||||||
final Stanza stanza;
|
final Stanza stanza;
|
||||||
|
|
||||||
|
/// The extension data used for constructing the stanza.
|
||||||
|
final TypedMap<StanzaHandlerExtension>? extensions;
|
||||||
|
|
||||||
/// Flag indicating whether a stanza id should be added before sending.
|
/// Flag indicating whether a stanza id should be added before sending.
|
||||||
final bool addId;
|
final bool addId;
|
||||||
|
|
||||||
/// 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 +48,62 @@ 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 {}
|
||||||
|
|
||||||
|
const _stanzaNotDefined = Object();
|
||||||
|
|
||||||
class Stanza extends XMLNode {
|
class Stanza extends XMLNode {
|
||||||
// ignore: use_super_parameters
|
// ignore: use_super_parameters
|
||||||
Stanza({
|
Stanza({
|
||||||
@@ -173,7 +218,7 @@ class Stanza extends XMLNode {
|
|||||||
|
|
||||||
Stanza copyWith({
|
Stanza copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? from,
|
Object? from = _stanzaNotDefined,
|
||||||
String? to,
|
String? to,
|
||||||
String? type,
|
String? type,
|
||||||
List<XMLNode>? children,
|
List<XMLNode>? children,
|
||||||
@@ -182,7 +227,7 @@ class Stanza extends XMLNode {
|
|||||||
return Stanza(
|
return Stanza(
|
||||||
tag: tag,
|
tag: tag,
|
||||||
to: to ?? this.to,
|
to: to ?? this.to,
|
||||||
from: from ?? this.from,
|
from: from != _stanzaNotDefined ? from as String? : this.from,
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
children: children ?? this.children,
|
children: children ?? this.children,
|
||||||
@@ -205,15 +250,14 @@ XMLNode buildErrorElement(String type, String condition, {String? text}) {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: condition,
|
tag: condition,
|
||||||
xmlns: fullStanzaXmlns,
|
xmlns: fullStanzaXmlns,
|
||||||
children: text != null
|
children: [
|
||||||
? [
|
if (text != null)
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'text',
|
tag: 'text',
|
||||||
xmlns: fullStanzaXmlns,
|
xmlns: fullStanzaXmlns,
|
||||||
text: text,
|
text: text,
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
: [],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
97
packages/moxxmpp/lib/src/util/incoming_queue.dart
Normal file
97
packages/moxxmpp/lib/src/util/incoming_queue.dart
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxxmpp/src/awaiter.dart';
|
||||||
|
import 'package:moxxmpp/src/parser.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
|
/// A queue for incoming [XMPPStreamObject]s to ensure "in order"
|
||||||
|
/// processing (except for stanzas that are awaited).
|
||||||
|
class IncomingStanzaQueue {
|
||||||
|
IncomingStanzaQueue(this._callback, this._stanzaAwaiter);
|
||||||
|
|
||||||
|
/// The queue for storing the completer of each
|
||||||
|
/// incoming stanza (or stream object to be precise).
|
||||||
|
/// Only access while holding the lock [_lock].
|
||||||
|
final Queue<Completer<void>> _queue = Queue();
|
||||||
|
|
||||||
|
/// Flag indicating whether a callback is already running (true)
|
||||||
|
/// or not. "a callback" and not "the callback" because awaited stanzas
|
||||||
|
/// are allowed to bypass the queue.
|
||||||
|
/// Only access while holding the lock [_lock].
|
||||||
|
bool _isRunning = false;
|
||||||
|
|
||||||
|
/// The function to call to process an incoming stream object.
|
||||||
|
final Future<void> Function(XMPPStreamObject) _callback;
|
||||||
|
|
||||||
|
/// Lock guarding both [_queue] and [_isRunning].
|
||||||
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
|
/// Logger.
|
||||||
|
final Logger _log = Logger('IncomingStanzaQueue');
|
||||||
|
|
||||||
|
final StanzaAwaiter _stanzaAwaiter;
|
||||||
|
|
||||||
|
Future<void> _processStreamObject(
|
||||||
|
Future<void>? future,
|
||||||
|
XMPPStreamObject object,
|
||||||
|
) async {
|
||||||
|
if (future == null) {
|
||||||
|
if (object is XMPPStreamElement) {
|
||||||
|
_log.finest(
|
||||||
|
'Bypassing queue for ${object.node.tag} (${object.node.attributes["id"]})',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _callback(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for our turn.
|
||||||
|
await future;
|
||||||
|
|
||||||
|
// Run the callback.
|
||||||
|
await _callback(object);
|
||||||
|
|
||||||
|
// Run the next entry.
|
||||||
|
await _lock.synchronized(() {
|
||||||
|
if (_queue.isNotEmpty) {
|
||||||
|
_queue.removeFirst().complete();
|
||||||
|
} else {
|
||||||
|
_isRunning = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addStanza(List<XMPPStreamObject> objects) async {
|
||||||
|
await _lock.synchronized(() async {
|
||||||
|
for (final object in objects) {
|
||||||
|
if (await canBypassQueue(object)) {
|
||||||
|
unawaited(
|
||||||
|
_processStreamObject(null, object),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer = Completer<void>();
|
||||||
|
if (_isRunning) {
|
||||||
|
_queue.add(completer);
|
||||||
|
} else {
|
||||||
|
_isRunning = true;
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
_processStreamObject(completer.future, object),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> canBypassQueue(XMPPStreamObject object) async {
|
||||||
|
if (object is XMPPStreamHeader) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
object as XMPPStreamElement;
|
||||||
|
return _stanzaAwaiter.isAwaited(object.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,57 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
|
|
||||||
/// NOTE: Specified by https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-extensible-file-thumbnails.md
|
|
||||||
|
|
||||||
const fileThumbnailsXmlns = 'proto:urn:xmpp:eft:0';
|
|
||||||
const blurhashThumbnailType = '$fileThumbnailsXmlns:blurhash';
|
|
||||||
|
|
||||||
abstract class Thumbnail {}
|
|
||||||
|
|
||||||
class BlurhashThumbnail extends Thumbnail {
|
|
||||||
BlurhashThumbnail(this.hash);
|
|
||||||
final String hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thumbnail? parseFileThumbnailElement(XMLNode node) {
|
|
||||||
assert(
|
|
||||||
node.attributes['xmlns'] == fileThumbnailsXmlns,
|
|
||||||
'Invalid element xmlns',
|
|
||||||
);
|
|
||||||
assert(node.tag == 'file-thumbnail', 'Invalid element name');
|
|
||||||
|
|
||||||
switch (node.attributes['type']!) {
|
|
||||||
case blurhashThumbnailType:
|
|
||||||
{
|
|
||||||
final hash = node.firstTag('blurhash')!.innerText();
|
|
||||||
return BlurhashThumbnail(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
XMLNode? _fromThumbnail(Thumbnail thumbnail) {
|
|
||||||
if (thumbnail is BlurhashThumbnail) {
|
|
||||||
return XMLNode(
|
|
||||||
tag: 'blurhash',
|
|
||||||
text: thumbnail.hash,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
XMLNode constructFileThumbnailElement(Thumbnail thumbnail) {
|
|
||||||
final node = _fromThumbnail(thumbnail)!;
|
|
||||||
var type = '';
|
|
||||||
if (thumbnail is BlurhashThumbnail) {
|
|
||||||
type = blurhashThumbnailType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return XMLNode.xmlns(
|
|
||||||
tag: 'file-thumbnail',
|
|
||||||
xmlns: fileThumbnailsXmlns,
|
|
||||||
attributes: {'type': type},
|
|
||||||
children: [node],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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';
|
||||||
|
|
||||||
@@ -10,14 +10,14 @@ class DataFormOption {
|
|||||||
XMLNode toXml() {
|
XMLNode toXml() {
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'option',
|
tag: 'option',
|
||||||
attributes: label != null
|
attributes: {
|
||||||
? <String, dynamic>{'label': label}
|
if (label != null) 'label': label,
|
||||||
: <String, dynamic>{},
|
},
|
||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'value',
|
tag: 'value',
|
||||||
text: value,
|
text: value,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -45,19 +45,22 @@ class DataFormField {
|
|||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'field',
|
tag: 'field',
|
||||||
attributes: <String, dynamic>{
|
attributes: <String, dynamic>{
|
||||||
...varAttr != null
|
if (varAttr != null) 'var': varAttr,
|
||||||
? <String, dynamic>{'var': varAttr}
|
if (type != null) 'type': type,
|
||||||
: <String, dynamic>{},
|
if (label != null) 'label': label,
|
||||||
...type != null ? <String, dynamic>{'type': type} : <String, dynamic>{},
|
|
||||||
...label != null
|
|
||||||
? <String, dynamic>{'label': label}
|
|
||||||
: <String, dynamic>{}
|
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
...description != null ? [XMLNode(tag: 'desc', text: description)] : [],
|
if (description != null)
|
||||||
...isRequired ? [XMLNode(tag: 'required')] : [],
|
XMLNode(
|
||||||
|
tag: 'desc',
|
||||||
|
text: description,
|
||||||
|
),
|
||||||
|
if (isRequired)
|
||||||
|
XMLNode(
|
||||||
|
tag: 'required',
|
||||||
|
),
|
||||||
...values.map((value) => XMLNode(tag: 'value', text: value)),
|
...values.map((value) => XMLNode(tag: 'value', text: value)),
|
||||||
...options.map((option) => option.toXml())
|
...options.map((option) => option.toXml()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -80,7 +83,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 {}
|
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ Stanza buildDiscoInfoQueryStanza(JID entity, String? node) {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: discoInfoXmlns,
|
xmlns: discoInfoXmlns,
|
||||||
attributes: node != null ? {'node': node} : {},
|
attributes: {
|
||||||
)
|
if (node != null) 'node': node,
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -27,8 +29,10 @@ Stanza buildDiscoItemsQueryStanza(JID entity, {String? node}) {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: discoItemsXmlns,
|
xmlns: discoItemsXmlns,
|
||||||
attributes: node != null ? {'node': node} : {},
|
attributes: {
|
||||||
)
|
if (node != null) 'node': node,
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ class Identity {
|
|||||||
XMLNode toXMLNode() {
|
XMLNode toXMLNode() {
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'identity',
|
tag: 'identity',
|
||||||
attributes: <String, dynamic>{
|
attributes: {
|
||||||
'category': category,
|
'category': category,
|
||||||
'type': type,
|
'type': type,
|
||||||
'name': name,
|
'name': name,
|
||||||
...lang == null
|
if (lang != null) 'xml:lang': lang,
|
||||||
? <String, dynamic>{}
|
|
||||||
: <String, dynamic>{'xml:lang': lang}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
25
packages/moxxmpp/lib/src/xeps/xep_0045/errors.dart
Normal file
25
packages/moxxmpp/lib/src/xeps/xep_0045/errors.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/// Represents an error related to Multi-User Chat (MUC).
|
||||||
|
abstract class MUCError {}
|
||||||
|
|
||||||
|
/// Error indicating an invalid (non-supported) stanza received while going
|
||||||
|
/// through normal operation/flow of an MUC.
|
||||||
|
class InvalidStanzaFormat extends MUCError {}
|
||||||
|
|
||||||
|
/// Represents an error indicating an abnormal condition while parsing
|
||||||
|
/// the DiscoInfo response stanza in Multi-User Chat (MUC).
|
||||||
|
class InvalidDiscoInfoResponse extends MUCError {}
|
||||||
|
|
||||||
|
/// Returned when no nickname was specified from the client side while trying to
|
||||||
|
/// perform some actions on the MUC, such as joining the room.
|
||||||
|
class NoNicknameSpecified extends MUCError {}
|
||||||
|
|
||||||
|
/// This error occurs when a user attempts to perform an action that requires
|
||||||
|
/// them to be a member of a room, but they are not currently joined to
|
||||||
|
/// that room.
|
||||||
|
class RoomNotJoinedError extends MUCError {}
|
||||||
|
|
||||||
|
/// Indicates that the MUC forbids us from joining, i.e. when we're banned.
|
||||||
|
class JoinForbiddenError extends MUCError {}
|
||||||
|
|
||||||
|
/// Indicates that an unspecific error occurred while joining.
|
||||||
|
class MUCUnspecificError extends MUCError {}
|
||||||
72
packages/moxxmpp/lib/src/xeps/xep_0045/events.dart
Normal file
72
packages/moxxmpp/lib/src/xeps/xep_0045/events.dart
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||||
|
|
||||||
|
/// Triggered when the MUC changes our nickname.
|
||||||
|
class OwnDataChangedEvent extends XmppEvent {
|
||||||
|
OwnDataChangedEvent(
|
||||||
|
this.roomJid,
|
||||||
|
this.nick,
|
||||||
|
this.affiliation,
|
||||||
|
this.role,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// Our nickname.
|
||||||
|
final String nick;
|
||||||
|
|
||||||
|
/// Our affiliation.
|
||||||
|
final Affiliation affiliation;
|
||||||
|
|
||||||
|
/// Our role.
|
||||||
|
final Role role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when an entity joins the MUC.
|
||||||
|
class MemberJoinedEvent extends XmppEvent {
|
||||||
|
MemberJoinedEvent(this.roomJid, this.member);
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// The new member.
|
||||||
|
final RoomMember member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when an entity changes their presence in the MUC.
|
||||||
|
class MemberChangedEvent extends XmppEvent {
|
||||||
|
MemberChangedEvent(this.roomJid, this.member);
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// The new member.
|
||||||
|
final RoomMember member;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when an entity leaves the MUC.
|
||||||
|
class MemberLeftEvent extends XmppEvent {
|
||||||
|
MemberLeftEvent(this.roomJid, this.nick);
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// The nick of the user who left.
|
||||||
|
final String nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when an entity changes their nick.
|
||||||
|
class MemberChangedNickEvent extends XmppEvent {
|
||||||
|
MemberChangedNickEvent(this.roomJid, this.oldNick, this.newNick);
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// The original nick.
|
||||||
|
final String oldNick;
|
||||||
|
|
||||||
|
/// The new nick.
|
||||||
|
final String newNick;
|
||||||
|
}
|
||||||
2
packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart
Normal file
2
packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const selfPresenceStatus = '110';
|
||||||
|
const nicknameChangedStatus = '303';
|
||||||
163
packages/moxxmpp/lib/src/xeps/xep_0045/types.dart
Normal file
163
packages/moxxmpp/lib/src/xeps/xep_0045/types.dart
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
|
|
||||||
|
class InvalidAffiliationException implements Exception {}
|
||||||
|
|
||||||
|
class InvalidRoleException implements Exception {}
|
||||||
|
|
||||||
|
enum Affiliation {
|
||||||
|
owner('owner'),
|
||||||
|
admin('admin'),
|
||||||
|
member('member'),
|
||||||
|
outcast('outcast'),
|
||||||
|
none('none');
|
||||||
|
|
||||||
|
const Affiliation(this.value);
|
||||||
|
|
||||||
|
factory Affiliation.fromString(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'owner':
|
||||||
|
return Affiliation.owner;
|
||||||
|
case 'admin':
|
||||||
|
return Affiliation.admin;
|
||||||
|
case 'member':
|
||||||
|
return Affiliation.member;
|
||||||
|
case 'outcast':
|
||||||
|
return Affiliation.outcast;
|
||||||
|
case 'none':
|
||||||
|
return Affiliation.none;
|
||||||
|
default:
|
||||||
|
throw InvalidAffiliationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value to use for an attribute referring to this affiliation.
|
||||||
|
final String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Role {
|
||||||
|
moderator('moderator'),
|
||||||
|
participant('participant'),
|
||||||
|
visitor('visitor'),
|
||||||
|
none('none');
|
||||||
|
|
||||||
|
const Role(this.value);
|
||||||
|
|
||||||
|
factory Role.fromString(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'moderator':
|
||||||
|
return Role.moderator;
|
||||||
|
case 'participant':
|
||||||
|
return Role.participant;
|
||||||
|
case 'visitor':
|
||||||
|
return Role.visitor;
|
||||||
|
case 'none':
|
||||||
|
return Role.none;
|
||||||
|
default:
|
||||||
|
throw InvalidRoleException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value to use for an attribute referring to this role.
|
||||||
|
final String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomInformation {
|
||||||
|
/// Represents information about a Multi-User Chat (MUC) room.
|
||||||
|
RoomInformation({
|
||||||
|
required this.jid,
|
||||||
|
required this.features,
|
||||||
|
required this.name,
|
||||||
|
this.roomInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Constructs a [RoomInformation] object from a [DiscoInfo] object.
|
||||||
|
/// The [DiscoInfo] object contains the necessary information to populate
|
||||||
|
/// the [RoomInformation] fields.
|
||||||
|
factory RoomInformation.fromDiscoInfo({
|
||||||
|
required DiscoInfo discoInfo,
|
||||||
|
}) =>
|
||||||
|
RoomInformation(
|
||||||
|
jid: discoInfo.jid!,
|
||||||
|
features: discoInfo.features,
|
||||||
|
name: discoInfo.identities
|
||||||
|
.firstWhere((i) => i.category == 'conference')
|
||||||
|
.name!,
|
||||||
|
roomInfo: discoInfo.extendedInfo.firstWhereOrNull((form) {
|
||||||
|
final field = form.getFieldByVar(formVarFormType);
|
||||||
|
return field?.type == 'hidden' &&
|
||||||
|
field?.values.first == roomInfoFormType;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The JID of the Multi-User Chat (MUC) room.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// A list of features supported by the Multi-User Chat (MUC) room.
|
||||||
|
final List<String> features;
|
||||||
|
|
||||||
|
/// The name or title of the Multi-User Chat (MUC) room.
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/// The data form containing room information.
|
||||||
|
final DataForm? roomInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The used message-id and an optional origin-id.
|
||||||
|
typedef PendingMessage = (String, String?);
|
||||||
|
|
||||||
|
/// An entity inside a MUC room. The name "member" here does not refer to an affiliation of member.
|
||||||
|
class RoomMember {
|
||||||
|
const RoomMember(this.nick, this.affiliation, this.role);
|
||||||
|
|
||||||
|
/// The entity's nickname.
|
||||||
|
final String nick;
|
||||||
|
|
||||||
|
/// The assigned affiliation.
|
||||||
|
final Affiliation affiliation;
|
||||||
|
|
||||||
|
/// The assigned role.
|
||||||
|
final Role role;
|
||||||
|
|
||||||
|
RoomMember copyWith({
|
||||||
|
String? nick,
|
||||||
|
Affiliation? affiliation,
|
||||||
|
Role? role,
|
||||||
|
}) {
|
||||||
|
return RoomMember(
|
||||||
|
nick ?? this.nick,
|
||||||
|
affiliation ?? this.affiliation,
|
||||||
|
role ?? this.role,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomState {
|
||||||
|
RoomState({required this.roomJid, this.nick, required this.joined}) {
|
||||||
|
pendingMessages = List<PendingMessage>.empty(growable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The JID of the room.
|
||||||
|
final JID roomJid;
|
||||||
|
|
||||||
|
/// The nick we're joined with.
|
||||||
|
String? nick;
|
||||||
|
|
||||||
|
/// Flag whether we're joined and can process messages
|
||||||
|
bool joined;
|
||||||
|
|
||||||
|
/// Our own affiliation inside the MUC.
|
||||||
|
Affiliation? affiliation;
|
||||||
|
|
||||||
|
/// Our own role inside the MUC.
|
||||||
|
Role? role;
|
||||||
|
|
||||||
|
/// The list of messages that we sent and are waiting for their echo.
|
||||||
|
late final List<PendingMessage> pendingMessages;
|
||||||
|
|
||||||
|
/// "List" of entities inside the MUC.
|
||||||
|
final Map<String, RoomMember> members = {};
|
||||||
|
}
|
||||||
512
packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart
Normal file
512
packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/presence.dart';
|
||||||
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0045/events.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0045/status_codes.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
|
/// (Room JID, nickname)
|
||||||
|
typedef MUCRoomJoin = (JID, String);
|
||||||
|
|
||||||
|
class MUCManager extends XmppManagerBase {
|
||||||
|
MUCManager() : super(mucManager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
/// Map a room's JID to its RoomState
|
||||||
|
final Map<JID, RoomState> _mucRoomCache = {};
|
||||||
|
|
||||||
|
/// Mapp a room's JID to a completer waiting for the completion of the join process.
|
||||||
|
final Map<JID, Completer<Result<bool, MUCError>>> _mucRoomJoinCompleter = {};
|
||||||
|
|
||||||
|
/// Cache lock
|
||||||
|
final Lock _cacheLock = Lock();
|
||||||
|
|
||||||
|
/// Flag indicating whether we joined the rooms added to the room list with
|
||||||
|
/// [prepareRoomList].
|
||||||
|
bool _joinedPreparedRooms = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'message',
|
||||||
|
callback: _onMessage,
|
||||||
|
// Before the message handler
|
||||||
|
priority: -99,
|
||||||
|
),
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'presence',
|
||||||
|
callback: _onPresence,
|
||||||
|
tagName: 'x',
|
||||||
|
tagXmlns: mucUserXmlns,
|
||||||
|
// Before the PresenceManager
|
||||||
|
priority: PresenceManager.presenceHandlerPriority + 1,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StanzaHandler> getOutgoingPreStanzaHandlers() => [
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'message',
|
||||||
|
callback: _onMessageSent,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
|
if (event is! StreamNegotiationsDoneEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only attempt rejoining if we did not resume the stream and all
|
||||||
|
// prepared rooms are already joined.
|
||||||
|
if (event.resumed && _joinedPreparedRooms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final mucJoins = List<MUCRoomJoin>.empty(growable: true);
|
||||||
|
await _cacheLock.synchronized(() async {
|
||||||
|
// Mark all groupchats as not joined.
|
||||||
|
for (final jid in _mucRoomCache.keys) {
|
||||||
|
_mucRoomCache[jid]!.joined = false;
|
||||||
|
_mucRoomJoinCompleter[jid] = Completer();
|
||||||
|
|
||||||
|
// Re-join all MUCs.
|
||||||
|
final state = _mucRoomCache[jid]!;
|
||||||
|
mucJoins.add((jid, state.nick!));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final join in mucJoins) {
|
||||||
|
final (jid, nick) = join;
|
||||||
|
await _sendMucJoin(
|
||||||
|
jid,
|
||||||
|
nick,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_joinedPreparedRooms = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares the internal room list to ensure that the rooms
|
||||||
|
/// [rooms] are joined once we are connected.
|
||||||
|
Future<void> prepareRoomList(List<MUCRoomJoin> rooms) async {
|
||||||
|
assert(
|
||||||
|
rooms.isNotEmpty,
|
||||||
|
'The room list should not be empty',
|
||||||
|
);
|
||||||
|
|
||||||
|
await _cacheLock.synchronized(() {
|
||||||
|
_joinedPreparedRooms = false;
|
||||||
|
for (final room in rooms) {
|
||||||
|
final (roomJid, nick) = room;
|
||||||
|
_mucRoomCache[roomJid] = RoomState(
|
||||||
|
roomJid: roomJid,
|
||||||
|
nick: nick,
|
||||||
|
joined: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queries the information of a Multi-User Chat room.
|
||||||
|
///
|
||||||
|
/// Retrieves the information about the specified MUC room by performing a
|
||||||
|
/// disco info query. Returns a [Result] with the [RoomInformation] on success
|
||||||
|
/// or an appropriate [MUCError] on failure.
|
||||||
|
Future<Result<RoomInformation, MUCError>> queryRoomInformation(
|
||||||
|
JID roomJID,
|
||||||
|
) async {
|
||||||
|
final result = await getAttributes()
|
||||||
|
.getManagerById<DiscoManager>(discoManager)!
|
||||||
|
.discoInfoQuery(roomJID);
|
||||||
|
if (result.isType<StanzaError>()) {
|
||||||
|
return Result(InvalidStanzaFormat());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final roomInformation = RoomInformation.fromDiscoInfo(
|
||||||
|
discoInfo: result.get<DiscoInfo>(),
|
||||||
|
);
|
||||||
|
return Result(roomInformation);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warning('Invalid disco information: $e');
|
||||||
|
return Result(InvalidDiscoInfoResponse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joins a Multi-User Chat room.
|
||||||
|
///
|
||||||
|
/// Joins the specified MUC room using the provided nickname. Sends a presence
|
||||||
|
/// stanza with the appropriate attributes to join the room. Returns a [Result]
|
||||||
|
/// with a boolean value indicating success or failure, or an [MUCError]
|
||||||
|
/// if applicable.
|
||||||
|
Future<Result<bool, MUCError>> joinRoom(
|
||||||
|
JID roomJid,
|
||||||
|
String nick, {
|
||||||
|
int? maxHistoryStanzas,
|
||||||
|
}) async {
|
||||||
|
if (nick.isEmpty) {
|
||||||
|
return Result(NoNicknameSpecified());
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer =
|
||||||
|
await _cacheLock.synchronized<Completer<Result<bool, MUCError>>>(
|
||||||
|
() {
|
||||||
|
_mucRoomCache[roomJid] = RoomState(
|
||||||
|
roomJid: roomJid,
|
||||||
|
nick: nick,
|
||||||
|
joined: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
final completer = Completer<Result<bool, MUCError>>();
|
||||||
|
_mucRoomJoinCompleter[roomJid] = completer;
|
||||||
|
return completer;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await _sendMucJoin(roomJid, nick, maxHistoryStanzas);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendMucJoin(
|
||||||
|
JID roomJid,
|
||||||
|
String nick,
|
||||||
|
int? maxHistoryStanzas,
|
||||||
|
) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
to: roomJid.withResource(nick).toString(),
|
||||||
|
children: [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'x',
|
||||||
|
xmlns: mucXmlns,
|
||||||
|
children: [
|
||||||
|
if (maxHistoryStanzas != null)
|
||||||
|
XMLNode(
|
||||||
|
tag: 'history',
|
||||||
|
attributes: {
|
||||||
|
'maxstanzas': maxHistoryStanzas.toString(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leaves a Multi-User Chat room.
|
||||||
|
///
|
||||||
|
/// Leaves the specified MUC room by sending an 'unavailable' presence stanza.
|
||||||
|
/// Removes the corresponding room entry from the cache. Returns a [Result]
|
||||||
|
/// with a boolean value indicating success or failure, or an [MUCError]
|
||||||
|
/// if applicable.
|
||||||
|
Future<Result<bool, MUCError>> leaveRoom(
|
||||||
|
JID roomJid,
|
||||||
|
) async {
|
||||||
|
final nick = await _cacheLock.synchronized(() {
|
||||||
|
final nick = _mucRoomCache[roomJid]?.nick;
|
||||||
|
_mucRoomCache.remove(roomJid);
|
||||||
|
return nick;
|
||||||
|
});
|
||||||
|
if (nick == null) {
|
||||||
|
return Result(RoomNotJoinedError());
|
||||||
|
}
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
to: roomJid.withResource(nick).toString(),
|
||||||
|
type: 'unavailable',
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return const Result(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<RoomState?> getRoomState(JID roomJid) async {
|
||||||
|
return _cacheLock.synchronized(() => _mucRoomCache[roomJid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onPresence(
|
||||||
|
Stanza presence,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
if (presence.from == null) {
|
||||||
|
logger.finest('Ignoring presence as it has no from attribute');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
final from = JID.fromString(presence.from!);
|
||||||
|
final bareFrom = from.toBare();
|
||||||
|
return _cacheLock.synchronized(() {
|
||||||
|
logger.finest('Lock aquired for presence from ${presence.from}');
|
||||||
|
final room = _mucRoomCache[bareFrom];
|
||||||
|
if (room == null) {
|
||||||
|
logger.finest('Ignoring presence as it does not belong to a room');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from.resource.isEmpty) {
|
||||||
|
// TODO(Unknown): Handle presence from the room itself.
|
||||||
|
logger.finest('Ignoring presence as it has no resource');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presence.type == 'error') {
|
||||||
|
final errorTag = presence.firstTag('error')!;
|
||||||
|
final error = errorTag.firstTagByXmlns(fullStanzaXmlns)!;
|
||||||
|
Result<bool, MUCError> result;
|
||||||
|
if (error.tag == 'forbidden') {
|
||||||
|
result = Result(JoinForbiddenError());
|
||||||
|
} else {
|
||||||
|
result = Result(MUCUnspecificError());
|
||||||
|
}
|
||||||
|
|
||||||
|
_mucRoomCache.remove(bareFrom);
|
||||||
|
_mucRoomJoinCompleter[bareFrom]!.complete(result);
|
||||||
|
_mucRoomJoinCompleter.remove(bareFrom);
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
presence,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final x = presence.firstTag('x', xmlns: mucUserXmlns)!;
|
||||||
|
final item = x.firstTag('item')!;
|
||||||
|
final statuses = x
|
||||||
|
.findTags('status')
|
||||||
|
.map((s) => s.attributes['code']! as String)
|
||||||
|
.toList();
|
||||||
|
final role = Role.fromString(
|
||||||
|
item.attributes['role']! as String,
|
||||||
|
);
|
||||||
|
final affiliation = Affiliation.fromString(
|
||||||
|
item.attributes['affiliation']! as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (statuses.contains(selfPresenceStatus)) {
|
||||||
|
if (room.joined) {
|
||||||
|
if (room.nick != from.resource ||
|
||||||
|
room.affiliation != affiliation ||
|
||||||
|
room.role != role) {
|
||||||
|
// Notify us of the changed data.
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
OwnDataChangedEvent(
|
||||||
|
bareFrom,
|
||||||
|
from.resource,
|
||||||
|
affiliation,
|
||||||
|
role,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the data to make sure we're in sync with the MUC.
|
||||||
|
room
|
||||||
|
..nick = from.resource
|
||||||
|
..affiliation = affiliation
|
||||||
|
..role = role;
|
||||||
|
logger.finest('Self-presence handled');
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
presence,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presence.attributes['type'] == 'unavailable') {
|
||||||
|
if (role == Role.none) {
|
||||||
|
// Cannot happen while joining, so we assume we are joined
|
||||||
|
assert(
|
||||||
|
room.joined,
|
||||||
|
'Should not receive unavailable with role="none" while joining',
|
||||||
|
);
|
||||||
|
room.members.remove(from.resource);
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
MemberLeftEvent(
|
||||||
|
bareFrom,
|
||||||
|
from.resource,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (statuses.contains(nicknameChangedStatus)) {
|
||||||
|
assert(
|
||||||
|
room.joined,
|
||||||
|
'Should not receive nick change while joining',
|
||||||
|
);
|
||||||
|
final newNick = item.attributes['nick']! as String;
|
||||||
|
final member = RoomMember(
|
||||||
|
newNick,
|
||||||
|
Affiliation.fromString(
|
||||||
|
item.attributes['affiliation']! as String,
|
||||||
|
),
|
||||||
|
role,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove the old member.
|
||||||
|
room.members.remove(from.resource);
|
||||||
|
|
||||||
|
// Add the "new" member".
|
||||||
|
room.members[newNick] = member;
|
||||||
|
|
||||||
|
// Trigger an event.
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
MemberChangedNickEvent(
|
||||||
|
bareFrom,
|
||||||
|
from.resource,
|
||||||
|
newNick,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final member = RoomMember(
|
||||||
|
from.resource,
|
||||||
|
Affiliation.fromString(
|
||||||
|
item.attributes['affiliation']! as String,
|
||||||
|
),
|
||||||
|
role,
|
||||||
|
);
|
||||||
|
logger.finest('Got presence from ${from.resource} in $bareFrom');
|
||||||
|
if (room.joined) {
|
||||||
|
if (room.members.containsKey(from.resource)) {
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
MemberChangedEvent(
|
||||||
|
bareFrom,
|
||||||
|
member,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
MemberJoinedEvent(
|
||||||
|
bareFrom,
|
||||||
|
member,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
room.members[from.resource] = member;
|
||||||
|
logger.finest('${from.resource} added to the member list');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.finest('Ran through');
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
presence,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onMessageSent(
|
||||||
|
Stanza message,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
if (message.to == null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
final toJid = JID.fromString(message.to!);
|
||||||
|
|
||||||
|
return _cacheLock.synchronized(() {
|
||||||
|
if (!_mucRoomCache.containsKey(toJid)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
_mucRoomCache[toJid]!.pendingMessages.add(
|
||||||
|
(message.id!, state.extensions.get<StableIdData>()?.originId),
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onMessage(
|
||||||
|
Stanza message,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
final fromJid = JID.fromString(message.from!);
|
||||||
|
final roomJid = fromJid.toBare();
|
||||||
|
return _cacheLock.synchronized(() {
|
||||||
|
logger.finest('Lock aquired for message from ${message.from}');
|
||||||
|
final roomState = _mucRoomCache[roomJid];
|
||||||
|
if (roomState == null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type == 'groupchat' && message.firstTag('subject') != null) {
|
||||||
|
// The room subject marks the end of the join flow.
|
||||||
|
if (!roomState.joined) {
|
||||||
|
// Mark the room as joined.
|
||||||
|
_mucRoomCache[roomJid]!.joined = true;
|
||||||
|
_mucRoomJoinCompleter[roomJid]!.complete(
|
||||||
|
const Result(true),
|
||||||
|
);
|
||||||
|
_mucRoomJoinCompleter.remove(roomJid);
|
||||||
|
logger.finest('$roomJid is now joined');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Unknown): Signal the subject?
|
||||||
|
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
message,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (!roomState.joined) {
|
||||||
|
// Ignore the discussion history.
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
message,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is the message reflection.
|
||||||
|
if (message.id == null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
final pending =
|
||||||
|
(message.id!, state.extensions.get<StableIdData>()?.originId);
|
||||||
|
if (fromJid.resource == roomState.nick &&
|
||||||
|
roomState.pendingMessages.contains(pending)) {
|
||||||
|
// Silently drop the message.
|
||||||
|
roomState.pendingMessages.remove(pending);
|
||||||
|
|
||||||
|
// TODO(Unknown): Maybe send an event stating that we received the reflection.
|
||||||
|
return StanzaHandlerData(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
message,
|
||||||
|
state.extensions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class VCardManager extends XmppManagerBase {
|
|||||||
tagName: 'x',
|
tagName: 'x',
|
||||||
tagXmlns: vCardTempUpdate,
|
tagXmlns: vCardTempUpdate,
|
||||||
callback: _onPresence,
|
callback: _onPresence,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -56,27 +56,13 @@ class VCardManager extends XmppManagerBase {
|
|||||||
final x = presence.firstTag('x', xmlns: vCardTempUpdate)!;
|
final x = presence.firstTag('x', xmlns: vCardTempUpdate)!;
|
||||||
final hash = x.firstTag('photo')!.innerText();
|
final hash = x.firstTag('photo')!.innerText();
|
||||||
|
|
||||||
final from = JID.fromString(presence.from!).toBare().toString();
|
|
||||||
final lastHash = _lastHash[from];
|
|
||||||
if (lastHash != hash) {
|
|
||||||
_lastHash[from] = hash;
|
|
||||||
final vcardResult = await requestVCard(from);
|
|
||||||
|
|
||||||
if (vcardResult.isType<VCard>()) {
|
|
||||||
final binval = vcardResult.get<VCard>().photo?.binval;
|
|
||||||
if (binval != null) {
|
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
VCardAvatarUpdatedEvent(JID.fromString(from), binval, hash),
|
VCardAvatarUpdatedEvent(
|
||||||
|
JID.fromString(presence.from!),
|
||||||
|
hash,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
return state;
|
||||||
logger.warning('No avatar data found');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warning('Failed to retrieve vCard for $from');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return state..done = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VCardPhoto? _parseVCardPhoto(XMLNode? node) {
|
VCardPhoto? _parseVCardPhoto(XMLNode? node) {
|
||||||
@@ -98,17 +84,17 @@ class VCardManager extends XmppManagerBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<VCardError, VCard>> requestVCard(String jid) async {
|
Future<Result<VCardError, VCard>> requestVCard(JID jid) async {
|
||||||
final result = (await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
type: 'get',
|
type: 'get',
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'vCard',
|
tag: 'vCard',
|
||||||
xmlns: vCardTempXmlns,
|
xmlns: vCardTempXmlns,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
encrypted: true,
|
encrypted: true,
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -39,26 +38,20 @@ class PubSubPublishOptions {
|
|||||||
varAttr: 'FORM_TYPE',
|
varAttr: 'FORM_TYPE',
|
||||||
type: 'hidden',
|
type: 'hidden',
|
||||||
),
|
),
|
||||||
...accessModel != null
|
if (accessModel != null)
|
||||||
? [
|
|
||||||
DataFormField(
|
DataFormField(
|
||||||
options: [],
|
options: [],
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
values: [accessModel!],
|
values: [accessModel!],
|
||||||
varAttr: 'pubsub#access_model',
|
varAttr: 'pubsub#access_model',
|
||||||
)
|
),
|
||||||
]
|
if (maxItems != null)
|
||||||
: [],
|
|
||||||
...maxItems != null
|
|
||||||
? [
|
|
||||||
DataFormField(
|
DataFormField(
|
||||||
options: [],
|
options: [],
|
||||||
isRequired: false,
|
isRequired: false,
|
||||||
values: [maxItems!],
|
values: [maxItems!],
|
||||||
varAttr: 'pubsub#max_items',
|
varAttr: 'pubsub#max_items',
|
||||||
),
|
),
|
||||||
]
|
|
||||||
: [],
|
|
||||||
],
|
],
|
||||||
).toXml();
|
).toXml();
|
||||||
}
|
}
|
||||||
@@ -88,7 +81,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
tagName: 'event',
|
tagName: 'event',
|
||||||
tagXmlns: pubsubEventXmlns,
|
tagXmlns: pubsubEventXmlns,
|
||||||
callback: _onPubsubMessage,
|
callback: _onPubsubMessage,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -202,6 +195,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@@ -245,6 +239,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@@ -313,11 +308,11 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'item',
|
tag: 'item',
|
||||||
attributes: id != null
|
attributes: {
|
||||||
? <String, String>{'id': id}
|
if (id != null) 'id': id,
|
||||||
: <String, String>{},
|
},
|
||||||
children: [payload],
|
children: [payload],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (pubOptions != null)
|
if (pubOptions != null)
|
||||||
@@ -326,9 +321,10 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
children: [pubOptions.toXml()],
|
children: [pubOptions.toXml()],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (result.attributes['type'] != 'result') {
|
if (result.attributes['type'] != 'result') {
|
||||||
@@ -399,8 +395,9 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
|
|
||||||
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
||||||
JID jid,
|
JID jid,
|
||||||
String node,
|
String node, {
|
||||||
) async {
|
int? maxItems,
|
||||||
|
}) async {
|
||||||
final result = (await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
@@ -413,12 +410,16 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'items',
|
tag: 'items',
|
||||||
attributes: <String, String>{'node': node},
|
attributes: {
|
||||||
|
'node': node,
|
||||||
|
if (maxItems != null) 'max_items': maxItems.toString(),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@@ -443,7 +444,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, PubSubItem>> getItem(
|
Future<Result<PubSubError, PubSubItem>> getItem(
|
||||||
String jid,
|
JID jid,
|
||||||
String node,
|
String node,
|
||||||
String id,
|
String id,
|
||||||
) async {
|
) async {
|
||||||
@@ -451,7 +452,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
@@ -471,6 +472,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@@ -521,6 +523,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (form.attributes['type'] != 'result') {
|
if (form.attributes['type'] != 'result') {
|
||||||
@@ -550,6 +553,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
if (submit.attributes['type'] != 'result') {
|
if (submit.attributes['type'] != 'result') {
|
||||||
@@ -580,6 +584,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@@ -624,6 +629,7 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
shouldEncrypt: false,
|
||||||
),
|
),
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class OOBManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message manager
|
// Before the message manager
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,14 +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/types.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';
|
||||||
|
|
||||||
@@ -16,6 +13,7 @@ abstract class AvatarError {}
|
|||||||
|
|
||||||
class UnknownAvatarError extends AvatarError {}
|
class UnknownAvatarError extends AvatarError {}
|
||||||
|
|
||||||
|
/// The result of a successful query of a users avatar.
|
||||||
class UserAvatarData {
|
class UserAvatarData {
|
||||||
const UserAvatarData(this.base64, this.hash);
|
const UserAvatarData(this.base64, this.hash);
|
||||||
|
|
||||||
@@ -26,7 +24,9 @@ class UserAvatarData {
|
|||||||
final String hash;
|
final String hash;
|
||||||
|
|
||||||
/// The raw avatar data.
|
/// The raw avatar data.
|
||||||
List<int> get data => base64Decode(base64);
|
/// NOTE: Remove newlines because "Line feeds SHOULD NOT be added but MUST be accepted"
|
||||||
|
/// (https://xmpp.org/extensions/xep-0084.html#proto-data).
|
||||||
|
List<int> get data => base64Decode(base64.replaceAll('\n', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserAvatarMetadata {
|
class UserAvatarMetadata {
|
||||||
@@ -40,11 +40,7 @@ class UserAvatarMetadata {
|
|||||||
);
|
);
|
||||||
|
|
||||||
factory UserAvatarMetadata.fromXML(XMLNode node) {
|
factory UserAvatarMetadata.fromXML(XMLNode node) {
|
||||||
assert(
|
assert(node.tag == 'info', 'node must be an <info /> element');
|
||||||
node.tag == 'metadata' &&
|
|
||||||
node.attributes['xmlns'] == userAvatarMetadataXmlns,
|
|
||||||
'<metadata /> element required',
|
|
||||||
);
|
|
||||||
|
|
||||||
final width = node.attributes['width'] as String?;
|
final width = node.attributes['width'] as String?;
|
||||||
final height = node.attributes['height'] as String?;
|
final height = node.attributes['height'] as String?;
|
||||||
@@ -118,20 +114,44 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
||||||
/// successful. Null otherwise
|
/// successful. Null otherwise
|
||||||
Future<Result<AvatarError, UserAvatarData>> getUserAvatar(JID jid) async {
|
Future<Result<AvatarError, UserAvatarData>> getUserAvatarData(
|
||||||
|
JID jid,
|
||||||
|
String id,
|
||||||
|
) async {
|
||||||
final pubsub = _getPubSubManager();
|
final pubsub = _getPubSubManager();
|
||||||
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
final resultRaw = await pubsub.getItem(jid, userAvatarDataXmlns, id);
|
||||||
|
if (resultRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||||
|
|
||||||
|
final result = resultRaw.get<PubSubItem>();
|
||||||
|
return Result(
|
||||||
|
UserAvatarData(
|
||||||
|
result.payload.innerText(),
|
||||||
|
id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to fetch the latest item from the User Avatar metadata node. Returns the list of
|
||||||
|
/// metadata contained within it. The list may be empty.
|
||||||
|
///
|
||||||
|
/// If an error occured, returns an [AvatarError].
|
||||||
|
Future<Result<AvatarError, List<UserAvatarMetadata>>> getLatestMetadata(
|
||||||
|
JID jid,
|
||||||
|
) async {
|
||||||
|
final resultsRaw = await _getPubSubManager()
|
||||||
|
.getItems(jid, userAvatarMetadataXmlns, maxItems: 1);
|
||||||
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||||
|
|
||||||
final results = resultsRaw.get<List<PubSubItem>>();
|
final results = resultsRaw.get<List<PubSubItem>>();
|
||||||
if (results.isEmpty) return Result(UnknownAvatarError());
|
if (results.isEmpty) {
|
||||||
|
return Result(UnknownAvatarError());
|
||||||
|
}
|
||||||
|
|
||||||
final item = results[0];
|
|
||||||
return Result(
|
return Result(
|
||||||
UserAvatarData(
|
results.first.payload
|
||||||
item.payload.innerText(),
|
.findTags('info')
|
||||||
item.id,
|
.map(UserAvatarMetadata.fromXML)
|
||||||
),
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,22 +233,4 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the PubSub Id of an avatar after doing a disco#items query.
|
|
||||||
/// Note that this assumes that there is only one (1) item published on
|
|
||||||
/// the node.
|
|
||||||
Future<Result<AvatarError, String>> getAvatarId(JID jid) async {
|
|
||||||
final disco = getAttributes().getManagerById(discoManager)! as DiscoManager;
|
|
||||||
final response = await disco.discoItemsQuery(
|
|
||||||
jid,
|
|
||||||
node: userAvatarDataXmlns,
|
|
||||||
shouldEncrypt: false,
|
|
||||||
);
|
|
||||||
if (response.isType<DiscoError>()) return Result(UnknownAvatarError());
|
|
||||||
|
|
||||||
final items = response.get<List<DiscoItem>>();
|
|
||||||
if (items.isEmpty) return Result(UnknownAvatarError());
|
|
||||||
|
|
||||||
return Result(items.first.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ChatStateManager extends XmppManagerBase {
|
|||||||
callback: _onChatStateReceived,
|
callback: _onChatStateReceived,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:moxxmpp/src/stringxml.dart';
|
|||||||
import 'package:moxxmpp/src/util/list.dart';
|
import 'package:moxxmpp/src/util/list.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
import 'package:moxxmpp/src/xeps/xep_0004.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/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_0300.dart';
|
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
@@ -174,39 +173,20 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
Future<void> _performQuery(
|
||||||
Future<StanzaHandlerData> onPresence(
|
Stanza presence,
|
||||||
Stanza stanza,
|
String ver,
|
||||||
StanzaHandlerData state,
|
String hashFunctionName,
|
||||||
|
String capabilityNode,
|
||||||
|
JID from,
|
||||||
) async {
|
) async {
|
||||||
if (stanza.from == null) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
final from = JID.fromString(stanza.from!);
|
|
||||||
final c = stanza.firstTag('c', xmlns: capsXmlns)!;
|
|
||||||
|
|
||||||
final hashFunctionName = c.attributes['hash'] as String?;
|
|
||||||
final capabilityNode = c.attributes['node'] as String?;
|
|
||||||
final ver = c.attributes['ver'] as String?;
|
|
||||||
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we know of the hash
|
|
||||||
final isCached =
|
|
||||||
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
|
||||||
if (isCached) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
|
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
|
||||||
final discoRequest = await dm.discoInfoQuery(
|
final discoRequest = await dm.discoInfoQuery(
|
||||||
from,
|
from,
|
||||||
node: capabilityNode,
|
node: capabilityNode,
|
||||||
);
|
);
|
||||||
if (discoRequest.isType<DiscoError>()) {
|
if (discoRequest.isType<StanzaError>()) {
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
final discoInfo = discoRequest.get<DiscoInfo>();
|
final discoInfo = discoRequest.get<DiscoInfo>();
|
||||||
|
|
||||||
@@ -221,7 +201,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
discoInfo,
|
discoInfo,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the disco#info result according to XEP-0115 § 5.4
|
// Validate the disco#info result according to XEP-0115 § 5.4
|
||||||
@@ -235,7 +215,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one equal identity',
|
'Malformed disco#info response: More than one equal identity',
|
||||||
);
|
);
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +226,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one equal feature',
|
'Malformed disco#info response: More than one equal feature',
|
||||||
);
|
);
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +254,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: Extended Info FORM_TYPE contains more than one value(s) of different value.',
|
'Malformed disco#info response: Extended Info FORM_TYPE contains more than one value(s) of different value.',
|
||||||
);
|
);
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +269,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one Extended Disco Info forms with the same FORM_TYPE value',
|
'Malformed disco#info response: More than one Extended Disco Info forms with the same FORM_TYPE value',
|
||||||
);
|
);
|
||||||
return state;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the field type is hidden
|
// Check if the field type is hidden
|
||||||
@@ -326,7 +306,43 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
'Capability hash mismatch from $from: Received "$ver", expected "$computedCapabilityHash".',
|
'Capability hash mismatch from $from: Received "$ver", expected "$computedCapabilityHash".',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
Future<StanzaHandlerData> onPresence(
|
||||||
|
Stanza stanza,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
if (stanza.from == null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
final from = JID.fromString(stanza.from!);
|
||||||
|
final c = stanza.firstTag('c', xmlns: capsXmlns)!;
|
||||||
|
|
||||||
|
final hashFunctionName = c.attributes['hash'] as String?;
|
||||||
|
final capabilityNode = c.attributes['node'] as String?;
|
||||||
|
final ver = c.attributes['ver'] as String?;
|
||||||
|
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we know of the hash
|
||||||
|
final isCached =
|
||||||
|
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
||||||
|
if (isCached) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
unawaited(
|
||||||
|
_performQuery(
|
||||||
|
stanza,
|
||||||
|
ver,
|
||||||
|
hashFunctionName,
|
||||||
|
capabilityNode,
|
||||||
|
from,
|
||||||
|
),
|
||||||
|
);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
|
|||||||
callback: _onDeliveryRequestReceived,
|
callback: _onDeliveryRequestReceived,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
tagName: 'block',
|
tagName: 'block',
|
||||||
tagXmlns: blockingXmlns,
|
tagXmlns: blockingXmlns,
|
||||||
callback: _blockPush,
|
callback: _blockPush,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -107,10 +107,12 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
children: items.map((item) {
|
children: items.map((item) {
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'item',
|
tag: 'item',
|
||||||
attributes: <String, String>{'jid': item},
|
attributes: {
|
||||||
|
'jid': item,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -128,7 +130,7 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'unblock',
|
tag: 'unblock',
|
||||||
xmlns: blockingXmlns,
|
xmlns: blockingXmlns,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -152,11 +154,13 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
.map(
|
.map(
|
||||||
(item) => XMLNode(
|
(item) => XMLNode(
|
||||||
tag: 'item',
|
tag: 'item',
|
||||||
attributes: <String, String>{'jid': item},
|
attributes: {
|
||||||
|
'jid': item,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -174,7 +178,7 @@ class BlockingManager extends XmppManagerBase {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'blocklist',
|
tag: 'blocklist',
|
||||||
xmlns: blockingXmlns,
|
xmlns: blockingXmlns,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ class StreamManagementEnableNonza extends XMLNode {
|
|||||||
StreamManagementEnableNonza()
|
StreamManagementEnableNonza()
|
||||||
: super(
|
: super(
|
||||||
tag: 'enable',
|
tag: 'enable',
|
||||||
attributes: <String, String>{'xmlns': smXmlns, 'resume': 'true'},
|
attributes: {
|
||||||
|
'xmlns': smXmlns,
|
||||||
|
'resume': 'true',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,10 +16,10 @@ class StreamManagementResumeNonza extends XMLNode {
|
|||||||
StreamManagementResumeNonza(String id, int h)
|
StreamManagementResumeNonza(String id, int h)
|
||||||
: super(
|
: super(
|
||||||
tag: 'resume',
|
tag: 'resume',
|
||||||
attributes: <String, String>{
|
attributes: {
|
||||||
'xmlns': smXmlns,
|
'xmlns': smXmlns,
|
||||||
'previd': id,
|
'previd': id,
|
||||||
'h': h.toString()
|
'h': h.toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,7 +28,10 @@ class StreamManagementAckNonza extends XMLNode {
|
|||||||
StreamManagementAckNonza(int h)
|
StreamManagementAckNonza(int h)
|
||||||
: super(
|
: super(
|
||||||
tag: 'a',
|
tag: 'a',
|
||||||
attributes: <String, String>{'xmlns': smXmlns, 'h': h.toString()},
|
attributes: {
|
||||||
|
'xmlns': smXmlns,
|
||||||
|
'h': h.toString(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +39,7 @@ class StreamManagementRequestNonza extends XMLNode {
|
|||||||
StreamManagementRequestNonza()
|
StreamManagementRequestNonza()
|
||||||
: super(
|
: super(
|
||||||
tag: 'r',
|
tag: 'r',
|
||||||
attributes: <String, String>{
|
attributes: {
|
||||||
'xmlns': smXmlns,
|
'xmlns': smXmlns,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -74,6 +75,17 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
return acks;
|
return acks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onData() async {
|
||||||
|
// The ack timer does not matter if we are currently in the middle of receiving
|
||||||
|
// data.
|
||||||
|
await _ackLock.synchronized(() {
|
||||||
|
if (_pendingAcks > 0) {
|
||||||
|
_resetAckTimer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Called when a stanza has been acked to decide whether we should trigger a
|
/// Called when a stanza has been acked to decide whether we should trigger a
|
||||||
/// StanzaAckedEvent.
|
/// StanzaAckedEvent.
|
||||||
///
|
///
|
||||||
@@ -139,7 +151,7 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
nonzaTag: 'a',
|
nonzaTag: 'a',
|
||||||
nonzaXmlns: smXmlns,
|
nonzaXmlns: smXmlns,
|
||||||
callback: _handleAckResponse,
|
callback: _handleAckResponse,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -147,14 +159,14 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
callback: _onServerStanzaReceived,
|
callback: _onServerStanzaReceived,
|
||||||
priority: 9999,
|
priority: 9999,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [
|
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
callback: _onClientStanzaSent,
|
callback: _onClientStanzaSent,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -224,6 +236,12 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
_ackTimer = null;
|
_ackTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the ack timer.
|
||||||
|
void _resetAckTimer() {
|
||||||
|
_stopAckTimer();
|
||||||
|
_startAckTimer();
|
||||||
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Future<void> handleAckTimeout() async {
|
Future<void> handleAckTimeout() async {
|
||||||
_stopAckTimer();
|
_stopAckTimer();
|
||||||
@@ -306,12 +324,15 @@ 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
|
||||||
if (_pendingAcks > 0) {
|
if (_pendingAcks > 0) {
|
||||||
_stopAckTimer();
|
_resetAckTimer();
|
||||||
_startAckTimer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,15 +357,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 +425,29 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
if (isStreamManagementEnabled()) {
|
if (isStreamManagementEnabled()) {
|
||||||
|
final smData = state.extensions.get<StreamManagementData>();
|
||||||
|
logger.finest('Should count stanza: ${smData?.shouldCountStanza}');
|
||||||
|
if (smData?.shouldCountStanza ?? true) {
|
||||||
await _incrementC2S();
|
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 +455,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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
59
packages/moxxmpp/lib/src/xeps/xep_0264.dart
Normal file
59
packages/moxxmpp/lib/src/xeps/xep_0264.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
extension _StringToInt on String {
|
||||||
|
int toInt() => int.parse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class JingleContentThumbnail {
|
||||||
|
const JingleContentThumbnail(
|
||||||
|
this.uri,
|
||||||
|
this.mediaType,
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory JingleContentThumbnail.fromXML(XMLNode thumbnail) {
|
||||||
|
assert(
|
||||||
|
thumbnail.tag == 'thumbnail',
|
||||||
|
'thumbnail must be Jingle Content Thumbnail',
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
thumbnail.attributes['xmlns'] == jingleContentThumbnailXmlns,
|
||||||
|
'thumbnail must be Jingle Content Thumbnail',
|
||||||
|
);
|
||||||
|
|
||||||
|
return JingleContentThumbnail(
|
||||||
|
Uri.parse(thumbnail.attributes['uri']! as String),
|
||||||
|
thumbnail.attributes['media-type'] as String?,
|
||||||
|
(thumbnail.attributes['width'] as String?)?.toInt(),
|
||||||
|
(thumbnail.attributes['height'] as String?)?.toInt(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The URI of the thumbnail data.
|
||||||
|
final Uri uri;
|
||||||
|
|
||||||
|
/// The MIME type of the thumbnail
|
||||||
|
final String? mediaType;
|
||||||
|
|
||||||
|
/// The width of the thumbnail.
|
||||||
|
final int? width;
|
||||||
|
|
||||||
|
/// The height of the thumbnail.
|
||||||
|
final int? height;
|
||||||
|
|
||||||
|
/// Convert the thumbnail to its XML representation.
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'thumbnail',
|
||||||
|
xmlns: jingleContentThumbnailXmlns,
|
||||||
|
attributes: {
|
||||||
|
'uri': uri.toString(),
|
||||||
|
if (mediaType != null) 'media-type': mediaType!,
|
||||||
|
if (width != null) 'width': width.toString(),
|
||||||
|
if (height != null) 'height': height.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
tagXmlns: carbonsXmlns,
|
tagXmlns: carbonsXmlns,
|
||||||
callback: _onMessageSent,
|
callback: _onMessageSent,
|
||||||
priority: -98,
|
priority: -98,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -124,7 +124,7 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'enable',
|
tag: 'enable',
|
||||||
xmlns: carbonsXmlns,
|
xmlns: carbonsXmlns,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -154,7 +154,7 @@ class CarbonsManager extends XmppManagerBase {
|
|||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'disable',
|
tag: 'disable',
|
||||||
xmlns: carbonsXmlns,
|
xmlns: carbonsXmlns,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class LastMessageCorrectionManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class ChatMarkerManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class StableIdManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the MessageManager
|
// Before the MessageManager
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -127,7 +127,13 @@ class StableIdManager extends XmppManagerBase {
|
|||||||
TypedMap<StanzaHandlerExtension> extensions,
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
) {
|
) {
|
||||||
final data = extensions.get<StableIdData>();
|
final data = extensions.get<StableIdData>();
|
||||||
return data != null ? data.toXML() : [];
|
if (data?.originId != null) {
|
||||||
|
return [
|
||||||
|
data!.toOriginIdElement(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -161,9 +161,9 @@ class HttpFileUploadManager extends XmppManagerBase {
|
|||||||
attributes: {
|
attributes: {
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'size': filesize.toString(),
|
'size': filesize.toString(),
|
||||||
...contentType != null ? {'content-type': contentType} : {}
|
if (contentType != null) 'content-type': contentType,
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ abstract class OmemoError {}
|
|||||||
|
|
||||||
class UnknownOmemoError extends OmemoError {}
|
class UnknownOmemoError extends OmemoError {}
|
||||||
|
|
||||||
class InvalidAffixElementsException with Exception {}
|
class InvalidAffixElementsException implements Exception {}
|
||||||
|
|
||||||
|
/// Internal exception that is returned when the device list cannot be
|
||||||
|
/// fetched because the returned list is empty.
|
||||||
|
class EmptyDeviceListException implements OmemoError {}
|
||||||
|
|
||||||
class OmemoNotSupportedForContactException extends OmemoError {}
|
class OmemoNotSupportedForContactException extends OmemoError {}
|
||||||
|
|
||||||
class EncryptionFailedException with Exception {}
|
class EncryptionFailedException implements Exception {}
|
||||||
|
|
||||||
class InvalidEnvelopePayloadException with Exception {}
|
class InvalidEnvelopePayloadException implements Exception {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
.map(
|
||||||
|
(ek) => XMLNode(
|
||||||
tag: 'key',
|
tag: 'key',
|
||||||
attributes: <String, String>{
|
attributes: {
|
||||||
'rid': '${key.rid}',
|
'rid': ek.rid.toString(),
|
||||||
'kex': key.kex ? 'true' : 'false',
|
if (ek.kex) 'kex': 'true',
|
||||||
},
|
},
|
||||||
text: key.value,
|
text: ek.value,
|
||||||
);
|
),
|
||||||
|
)
|
||||||
if (keyElements.containsKey(key.jid)) {
|
.toList();
|
||||||
keyElements[key.jid]!.add(keyElement);
|
|
||||||
} else {
|
|
||||||
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 result = await om.onOutgoingStanza(
|
final encryptToJids = [
|
||||||
OmemoOutgoingStanza(
|
|
||||||
[
|
|
||||||
toJid.toString(),
|
toJid.toString(),
|
||||||
if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(),
|
if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(),
|
||||||
],
|
];
|
||||||
|
final result = await om.onOutgoingStanza(
|
||||||
|
omemo.OmemoOutgoingStanza(
|
||||||
|
encryptToJids,
|
||||||
_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,
|
|
||||||
|
keys.addAll(
|
||||||
|
keysElement.findTags('key').map(
|
||||||
|
(key) => omemo.EncryptedKey(
|
||||||
int.parse(key.attributes['rid']! as String),
|
int.parse(key.attributes['rid']! as String),
|
||||||
key.innerText(),
|
key.innerText(),
|
||||||
key.attributes['kex'] == 'true',
|
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,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,7 +535,10 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
|
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
|
||||||
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
return Result(result.get<List<PubSubItem>>().first.payload);
|
|
||||||
|
final itemList = result.get<List<PubSubItem>>();
|
||||||
|
if (itemList.isEmpty) return Result(EmptyDeviceListException());
|
||||||
|
return Result(itemList.first.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the OMEMO device list from [jid].
|
/// Retrieves the OMEMO device list from [jid].
|
||||||
@@ -532,7 +557,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,12 +578,12 @@ 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 {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final bareJid = jid.toBare().toString();
|
final bareJid = jid.toBare();
|
||||||
final item = await pm.getItem(bareJid, omemoBundlesXmlns, '$deviceId');
|
final item = await pm.getItem(bareJid, omemoBundlesXmlns, '$deviceId');
|
||||||
if (item.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (item.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
|
|
||||||
@@ -569,7 +594,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 +663,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.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
import 'package:moxxmpp/src/xeps/xep_0264.dart';
|
||||||
|
|
||||||
class StatelessMediaSharingData implements StanzaHandlerExtension {
|
class StatelessMediaSharingData implements StanzaHandlerExtension {
|
||||||
const StatelessMediaSharingData({
|
const StatelessMediaSharingData({
|
||||||
@@ -20,7 +20,7 @@ class StatelessMediaSharingData implements StanzaHandlerExtension {
|
|||||||
final int size;
|
final int size;
|
||||||
final String description;
|
final String description;
|
||||||
final Map<String, String> hashes; // algo -> hash value
|
final Map<String, String> hashes; // algo -> hash value
|
||||||
final List<Thumbnail> thumbnails;
|
final List<JingleContentThumbnail> thumbnails;
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
}
|
}
|
||||||
@@ -48,16 +48,11 @@ StatelessMediaSharingData parseSIMSElement(XMLNode node) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
final thumbnails = List<Thumbnail>.empty(growable: true);
|
// Thumbnails
|
||||||
for (final child in file.children) {
|
final thumbnails = List<JingleContentThumbnail>.empty(growable: true);
|
||||||
// TODO(Unknown): Handle other thumbnails
|
for (final i
|
||||||
if (child.tag == 'file-thumbnail' &&
|
in file.findTags('thumbnail', xmlns: jingleContentThumbnailXmlns)) {
|
||||||
child.attributes['xmlns'] == fileThumbnailsXmlns) {
|
thumbnails.add(JingleContentThumbnail.fromXML(i));
|
||||||
final thumb = parseFileThumbnailElement(child);
|
|
||||||
if (thumb != null) {
|
|
||||||
thumbnails.add(thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatelessMediaSharingData(
|
return StatelessMediaSharingData(
|
||||||
@@ -87,7 +82,7 @@ class SIMSManager extends XmppManagerBase {
|
|||||||
tagXmlns: referenceXmlns,
|
tagXmlns: referenceXmlns,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -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,6 +1,6 @@
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
/// A data class describing the user agent. See https://dyn.eightysoft.de/final/xep-0388.html#initiation
|
/// A data class describing the user agent. See https://xmpp.org/extensions/xep-0388.html#initiation.
|
||||||
class UserAgent {
|
class UserAgent {
|
||||||
const UserAgent({
|
const UserAgent({
|
||||||
this.id,
|
this.id,
|
||||||
@@ -24,11 +24,9 @@ class UserAgent {
|
|||||||
);
|
);
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'user-agent',
|
tag: 'user-agent',
|
||||||
attributes: id != null
|
attributes: {
|
||||||
? {
|
if (id != null) 'id': id,
|
||||||
'id': id,
|
},
|
||||||
}
|
|
||||||
: {},
|
|
||||||
children: [
|
children: [
|
||||||
if (software != null)
|
if (software != null)
|
||||||
XMLNode(
|
XMLNode(
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -20,12 +20,10 @@ enum Sasl2State {
|
|||||||
/// A negotiator that implements XEP-0388 SASL2. Alone, it does nothing. Has to be
|
/// A negotiator that implements XEP-0388 SASL2. Alone, it does nothing. Has to be
|
||||||
/// registered with other negotiators that register themselves against this one.
|
/// registered with other negotiators that register themselves against this one.
|
||||||
class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
||||||
Sasl2Negotiator({
|
Sasl2Negotiator() : super(100, false, sasl2Xmlns, sasl2Negotiator);
|
||||||
this.userAgent,
|
|
||||||
}) : super(100, false, sasl2Xmlns, sasl2Negotiator);
|
|
||||||
|
|
||||||
/// The user agent data that will be sent to the server when authenticating.
|
/// The user agent data that will be sent to the server when authenticating.
|
||||||
final UserAgent? userAgent;
|
UserAgent? userAgent;
|
||||||
|
|
||||||
/// List of callbacks that are registered against us. Will be called once we get
|
/// List of callbacks that are registered against us. Will be called once we get
|
||||||
/// SASL2 features.
|
/// SASL2 features.
|
||||||
|
|||||||
61
packages/moxxmpp/lib/src/xeps/xep_0421.dart
Normal file
61
packages/moxxmpp/lib/src/xeps/xep_0421.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
/// Representation of a <occupant-id /> element.
|
||||||
|
class OccupantIdData implements StanzaHandlerExtension {
|
||||||
|
const OccupantIdData(
|
||||||
|
this.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The unique occupant id.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'occupant-id',
|
||||||
|
xmlns: occupantIdXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OccupantIdManager extends XmppManagerBase {
|
||||||
|
OccupantIdManager() : super(occupantIdManager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getDiscoFeatures() => [
|
||||||
|
occupantIdXmlns,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'message',
|
||||||
|
tagName: 'occupant-id',
|
||||||
|
tagXmlns: occupantIdXmlns,
|
||||||
|
callback: _onMessage,
|
||||||
|
// Before the MessageManager
|
||||||
|
priority: MessageManager.messageHandlerPriority + 1,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onMessage(
|
||||||
|
Stanza stanza,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
return state
|
||||||
|
..extensions.set(OccupantIdData(stanza.attributes['id']! as String));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ class MessageRetractionManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the MessageManager
|
// Before the MessageManager
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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/staging/extensible_file_thumbnails.dart';
|
import 'package:moxxmpp/src/xeps/xep_0264.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
|
|
||||||
class FileMetadataData {
|
class FileMetadataData {
|
||||||
@@ -39,12 +39,10 @@ class FileMetadataData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnails
|
// Thumbnails
|
||||||
final thumbnails = List<Thumbnail>.empty(growable: true);
|
final thumbnails = List<JingleContentThumbnail>.empty(growable: true);
|
||||||
for (final i in node.findTags('file-thumbnail')) {
|
for (final i
|
||||||
final thumbnail = parseFileThumbnailElement(i);
|
in node.findTags('thumbnail', xmlns: jingleContentThumbnailXmlns)) {
|
||||||
if (thumbnail != null) {
|
thumbnails.add(JingleContentThumbnail.fromXML(i));
|
||||||
thumbnails.add(thumbnail);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Length and height
|
// Length and height
|
||||||
@@ -75,7 +73,7 @@ class FileMetadataData {
|
|||||||
final String? mediaType;
|
final String? mediaType;
|
||||||
final int? width;
|
final int? width;
|
||||||
final int? height;
|
final int? height;
|
||||||
final List<Thumbnail> thumbnails;
|
final List<JingleContentThumbnail> thumbnails;
|
||||||
final String? desc;
|
final String? desc;
|
||||||
final Map<HashFunction, String> hashes;
|
final Map<HashFunction, String> hashes;
|
||||||
final int? length;
|
final int? length;
|
||||||
@@ -119,7 +117,7 @@ class FileMetadataData {
|
|||||||
|
|
||||||
for (final thumbnail in thumbnails) {
|
for (final thumbnail in thumbnails) {
|
||||||
node.addChild(
|
node.addChild(
|
||||||
constructFileThumbnailElement(thumbnail),
|
thumbnail.toXML(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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?;
|
||||||
@@ -134,7 +133,7 @@ class SFSManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -98,
|
priority: -98,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -151,14 +150,17 @@ class SFSManager extends XmppManagerBase {
|
|||||||
// TODO(Unknown): Consider all sources?
|
// TODO(Unknown): Consider all sources?
|
||||||
final source = data.sources.first;
|
final source = data.sources.first;
|
||||||
OOBData? oob;
|
OOBData? oob;
|
||||||
|
MessageBodyData? body;
|
||||||
if (source is StatelessFileSharingUrlSource && data.includeOOBFallback) {
|
if (source is StatelessFileSharingUrlSource && data.includeOOBFallback) {
|
||||||
// SFS recommends OOB as a fallback
|
// SFS recommends OOB as a fallback
|
||||||
oob = OOBData(source.url, null);
|
oob = OOBData(source.url, null);
|
||||||
|
body = MessageBodyData(source.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
data.toXML(),
|
data.toXML(),
|
||||||
if (oob != null) oob.toXML(),
|
if (oob != null) oob.toXML(),
|
||||||
|
if (body != null) body.toXML(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -344,7 +344,7 @@ class StickersManager extends XmppManagerBase {
|
|||||||
) async {
|
) async {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final stickerPackDataRaw = await pm.getItem(
|
final stickerPackDataRaw = await pm.getItem(
|
||||||
jid.toBare().toString(),
|
jid.toBare(),
|
||||||
stickersXmlns,
|
stickersXmlns,
|
||||||
id,
|
id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -96,7 +96,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -99,
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -107,8 +107,10 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
TypedMap<StanzaHandlerExtension> extensions,
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
) {
|
) {
|
||||||
final data = extensions.get<ReplyData>();
|
final data = extensions.get<ReplyData>();
|
||||||
return data != null
|
if (data == null) {
|
||||||
? [
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'reply',
|
tag: 'reply',
|
||||||
xmlns: replyXmlns,
|
xmlns: replyXmlns,
|
||||||
@@ -127,7 +129,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
if (data.body != null)
|
if (data.body != null)
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'fallback',
|
tag: 'fallback',
|
||||||
xmlns: fallbackXmlns,
|
xmlns: fallbackIndicationXmlns,
|
||||||
attributes: {'for': replyXmlns},
|
attributes: {'for': replyXmlns},
|
||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
@@ -139,8 +141,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
];
|
||||||
: [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(
|
Future<StanzaHandlerData> _onMessage(
|
||||||
@@ -154,7 +155,8 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
int? end;
|
int? end;
|
||||||
|
|
||||||
// TODO(Unknown): Maybe extend firstTag to also look for attributes
|
// TODO(Unknown): Maybe extend firstTag to also look for attributes
|
||||||
final fallback = stanza.firstTag('fallback', xmlns: fallbackXmlns);
|
final fallback =
|
||||||
|
stanza.firstTag('fallback', xmlns: fallbackIndicationXmlns);
|
||||||
if (fallback != null) {
|
if (fallback != null) {
|
||||||
final body = fallback.firstTag('body')!;
|
final body = fallback.firstTag('body')!;
|
||||||
start = int.parse(body.attributes['start']! as String);
|
start = int.parse(body.attributes['start']! as String);
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
final Logger _log = Logger('FASTSaslNegotiator');
|
final Logger _log = Logger('FASTSaslNegotiator');
|
||||||
|
|
||||||
/// The token, if non-null, to use for authentication.
|
/// The token, if non-null, to use for authentication.
|
||||||
FASTToken? fastToken;
|
String? fastToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
@@ -100,11 +100,12 @@ class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||||
final token = response.firstTag('token', xmlns: fastXmlns);
|
final tokenElement = response.firstTag('token', xmlns: fastXmlns);
|
||||||
if (token != null) {
|
if (tokenElement != null) {
|
||||||
fastToken = FASTToken.fromXml(token);
|
final token = FASTToken.fromXml(tokenElement);
|
||||||
|
fastToken = token.token;
|
||||||
await attributes.sendEvent(
|
await attributes.sendEvent(
|
||||||
NewFASTTokenReceivedEvent(fastToken!),
|
NewFASTTokenReceivedEvent(token),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ class FASTSaslNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getRawStep(String input) async {
|
Future<String> getRawStep(String input) async {
|
||||||
return fastToken!.token;
|
return fastToken!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -1,29 +1,45 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
||||||
xmlns='http://usefulinc.com/ns/doap#'
|
xmlns="http://usefulinc.com/ns/doap#"
|
||||||
xmlns:foaf='http://xmlns.com/foaf/0.1/'
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
xmlns:xmpp='https://linkmauve.fr/ns/xmpp-doap#'>
|
xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"
|
||||||
<Project xml:lang='en'>
|
xmlns:schema="https://schema.org/">
|
||||||
|
<Project xml:lang="en">
|
||||||
<name>moxxmpp</name>
|
<name>moxxmpp</name>
|
||||||
<created>2021-12-26</created>
|
<created>2021-12-26</created>
|
||||||
<homepage rdf:resource='https://codeberg.org/moxxy/moxxmpp'/>
|
<homepage rdf:resource="https://codeberg.org/moxxy/moxxmpp"/>
|
||||||
<os>Linux</os>
|
<os>Linux</os>
|
||||||
<os>Windows</os>
|
<os>Windows</os>
|
||||||
<os>macOS</os>
|
<os>macOS</os>
|
||||||
<os>Android</os>
|
<os>Android</os>
|
||||||
<os>iOS</os>
|
<os>iOS</os>
|
||||||
|
|
||||||
<programming-language>Dart</programming-language>
|
|
||||||
|
|
||||||
<maintainer>
|
<maintainer>
|
||||||
<foaf:Person>
|
<foaf:Person>
|
||||||
<foaf:name>Alexander "Polynomdivision"</foaf:name>
|
<foaf:name>Alexander "Polynomdivision"</foaf:name>
|
||||||
<foaf:homepage rdf:resource="https://blog.polynom.me" />
|
<foaf:homepage rdf:resource="https://polynom.me"/>
|
||||||
</foaf:Person>
|
</foaf:Person>
|
||||||
</maintainer>
|
</maintainer>
|
||||||
|
|
||||||
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html" />
|
<!-- Channel list -->
|
||||||
|
<developer-forum rdf:resource='xmpp:dev@muc.moxxy.org?join'/>
|
||||||
|
<support-forum rdf:resource='xmpp:dev@muc.moxxy.org?join'/>
|
||||||
|
|
||||||
|
<!-- Repository information -->
|
||||||
|
<programming-language>Dart</programming-language>
|
||||||
|
<bug-database rdf:resource="https://codeberg.org/moxxy/moxxmpp/issues" />
|
||||||
|
<license rdf:resource="https://codeberg.org/moxxy/moxxmpp/src/branch/master/packages/moxxmpp/LICENSE" />
|
||||||
|
<repository>
|
||||||
|
<GitRepository>
|
||||||
|
<browse rdf:resource="https://codeberg.org/moxxy/moxxmpp" />
|
||||||
|
<location rdf:resource="https://codeberg.org/moxxy/moxxmpp.git" />
|
||||||
|
</GitRepository>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
<!-- RFC list -->
|
||||||
|
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html"/>
|
||||||
|
<implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html"/>
|
||||||
|
|
||||||
|
<!-- Standard XEP list -->
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
|
||||||
@@ -64,7 +80,6 @@
|
|||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note xml:lang="en">Receiving data</xmpp:note>
|
|
||||||
<xmpp:version>1.1.4</xmpp:version>
|
<xmpp:version>1.1.4</xmpp:version>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
@@ -101,7 +116,6 @@
|
|||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>1.3.0</xmpp:version>
|
<xmpp:version>1.3.0</xmpp:version>
|
||||||
<xmpp:note xml:lang="en">Not plugged into the UI</xmpp:note>
|
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
@@ -111,6 +125,13 @@
|
|||||||
<xmpp:version>1.6</xmpp:version>
|
<xmpp:version>1.6</xmpp:version>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
|
<implements>
|
||||||
|
<xmpp:SupportedXep>
|
||||||
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0264.html"/>
|
||||||
|
<xmpp:status>complete</xmpp:status>
|
||||||
|
<xmpp:version>0.4.1</xmpp:version>
|
||||||
|
</xmpp:SupportedXep>
|
||||||
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
|
||||||
@@ -145,7 +166,6 @@
|
|||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:note xml:lang="en">Read-only support</xmpp:note>
|
|
||||||
<xmpp:version>0.4</xmpp:version>
|
<xmpp:version>0.4</xmpp:version>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
@@ -153,7 +173,6 @@
|
|||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html"/>
|
||||||
<xmpp:status>complete</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:note xml:lang="en">Write-only support</xmpp:note>
|
|
||||||
<xmpp:version>0.3.0</xmpp:version>
|
<xmpp:version>0.3.0</xmpp:version>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
@@ -174,9 +193,8 @@
|
|||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>complete</xmpp:status>
|
||||||
<xmpp:version>1.1.0</xmpp:version>
|
<xmpp:version>1.1.0</xmpp:version>
|
||||||
<xmpp:note xml:lang="en">Only handles the success case; not accessible via the App</xmpp:note>
|
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
@@ -258,12 +276,14 @@
|
|||||||
</implements>
|
</implements>
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-extensible-file-thumbnails.md" />
|
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0484.html" />
|
||||||
<xmpp:status>partial</xmpp:status>
|
<xmpp:status>partial</xmpp:status>
|
||||||
<xmpp:version>0.2.1</xmpp:version>
|
<xmpp:version>0.1.1</xmpp:version>
|
||||||
<xmpp:note xml:lang="en">Only Blurhash is implemented</xmpp:note>
|
<xmpp:note xml:lang="en">Invalidation is never requested</xmpp:note>
|
||||||
</xmpp:SupportedXep>
|
</xmpp:SupportedXep>
|
||||||
</implements>
|
</implements>
|
||||||
|
|
||||||
|
<!-- Non-Standard (Proto) XEPs -->
|
||||||
<implements>
|
<implements>
|
||||||
<xmpp:SupportedXep>
|
<xmpp:SupportedXep>
|
||||||
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md"/>
|
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md"/>
|
||||||
|
|||||||
@@ -5,30 +5,28 @@ homepage: https://codeberg.org/moxxy/moxxmpp
|
|||||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.17.5 <3.0.0'
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
collection: ^1.16.0
|
collection: ^1.18.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.7.0
|
||||||
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.8.0
|
||||||
logging: ^1.0.2
|
logging: ^1.2.0
|
||||||
meta: ^1.7.0
|
meta: ^1.15.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.6.0
|
||||||
random_string: ^2.3.1
|
random_string: ^2.3.1
|
||||||
saslprep: ^1.0.2
|
saslprep: ^1.0.3
|
||||||
synchronized: ^3.0.0+2
|
synchronized: ^3.3.0+3
|
||||||
uuid: ^3.0.5
|
uuid: ^3.0.7
|
||||||
xml: ^6.1.0
|
xml: ^6.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.1.11
|
build_runner: ^2.4.12
|
||||||
test: ^1.16.0
|
test: ^1.25.8
|
||||||
very_good_analysis: ^3.0.1
|
very_good_analysis: ^6.0.0
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import 'package:moxxmpp/moxxmpp.dart';
|
|||||||
import 'package:moxxmpp/src/awaiter.dart';
|
import 'package:moxxmpp/src/awaiter.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
const bareJid = 'user4@example.org';
|
||||||
const bareJid = JID('moxxmpp', 'server3.example', '');
|
String getBareJidCallback() => bareJid;
|
||||||
|
|
||||||
|
void main() {
|
||||||
test('Test awaiting an awaited stanza with a from attribute', () async {
|
test('Test awaiting an awaited stanza with a from attribute', () async {
|
||||||
final awaiter = StanzaAwaiter();
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
// "Send" a stanza
|
// "Send" a stanza
|
||||||
final future = await awaiter.addPending(
|
final future = await awaiter.addPending(
|
||||||
@@ -20,14 +21,12 @@ void main() {
|
|||||||
XMLNode.fromString(
|
XMLNode.fromString(
|
||||||
'<iq from="user3@server.example" id="abc123" type="result" />',
|
'<iq from="user3@server.example" id="abc123" type="result" />',
|
||||||
),
|
),
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result1, false);
|
expect(result1, false);
|
||||||
final result2 = await awaiter.onData(
|
final result2 = await awaiter.onData(
|
||||||
XMLNode.fromString(
|
XMLNode.fromString(
|
||||||
'<iq from="user1@server.example" id="lol" type="result" />',
|
'<iq from="user1@server.example" id="lol" type="result" />',
|
||||||
),
|
),
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result2, false);
|
expect(result2, false);
|
||||||
|
|
||||||
@@ -37,22 +36,20 @@ void main() {
|
|||||||
);
|
);
|
||||||
final result3 = await awaiter.onData(
|
final result3 = await awaiter.onData(
|
||||||
stanza,
|
stanza,
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result3, true);
|
expect(result3, true);
|
||||||
expect(await future, stanza);
|
expect(await future, stanza);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test awaiting an awaited stanza without a from attribute', () async {
|
test('Test awaiting an awaited stanza without a from attribute', () async {
|
||||||
final awaiter = StanzaAwaiter();
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
// "Send" a stanza
|
// "Send" a stanza
|
||||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||||
|
|
||||||
// Receive the wrong answer
|
// Receive the wrong answer
|
||||||
final result1 = await awaiter.onData(
|
final result1 = await awaiter.onData(
|
||||||
XMLNode.fromString('<iq id="lol" type="result" />'),
|
XMLNode.fromString('<iq id="lol" type="result" />'),
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result1, false);
|
expect(result1, false);
|
||||||
|
|
||||||
@@ -60,23 +57,21 @@ void main() {
|
|||||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||||
final result2 = await awaiter.onData(
|
final result2 = await awaiter.onData(
|
||||||
stanza,
|
stanza,
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result2, true);
|
expect(result2, true);
|
||||||
expect(await future, stanza);
|
expect(await future, stanza);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test awaiting a stanza that was already awaited', () async {
|
test('Test awaiting a stanza that was already awaited', () async {
|
||||||
final awaiter = StanzaAwaiter();
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
// "Send" a stanza
|
// "Send" a stanza
|
||||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||||
|
|
||||||
// Receive the correct answer
|
// Receive the correct answer
|
||||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||||
final result1 = await awaiter.onData(
|
final result1 = await awaiter.onData(
|
||||||
stanza,
|
stanza,
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result1, true);
|
expect(result1, true);
|
||||||
expect(await future, stanza);
|
expect(await future, stanza);
|
||||||
@@ -84,31 +79,55 @@ void main() {
|
|||||||
// Receive it again
|
// Receive it again
|
||||||
final result2 = await awaiter.onData(
|
final result2 = await awaiter.onData(
|
||||||
stanza,
|
stanza,
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result2, false);
|
expect(result2, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Test ignoring a stanza that has the wrong tag', () async {
|
test('Test ignoring a stanza that has the wrong tag', () async {
|
||||||
final awaiter = StanzaAwaiter();
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
// "Send" a stanza
|
// "Send" a stanza
|
||||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||||
|
|
||||||
// Receive the wrong answer
|
// Receive the wrong answer
|
||||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||||
final result1 = await awaiter.onData(
|
final result1 = await awaiter.onData(
|
||||||
XMLNode.fromString('<message id="abc123" type="result" />'),
|
XMLNode.fromString('<message id="abc123" type="result" />'),
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result1, false);
|
expect(result1, false);
|
||||||
|
|
||||||
// Receive the correct answer
|
// Receive the correct answer
|
||||||
final result2 = await awaiter.onData(
|
final result2 = await awaiter.onData(
|
||||||
stanza,
|
stanza,
|
||||||
bareJid,
|
|
||||||
);
|
);
|
||||||
expect(result2, true);
|
expect(result2, true);
|
||||||
expect(await future, stanza);
|
expect(await future, stanza);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Sending a stanza to our bare JID', () async {
|
||||||
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
|
// "Send" a stanza
|
||||||
|
final future = await awaiter.addPending(bareJid, 'abc123', 'iq');
|
||||||
|
|
||||||
|
// Receive the response.
|
||||||
|
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||||
|
await awaiter.onData(stanza);
|
||||||
|
expect(await future, stanza);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Sending a stanza to our bare JID and receiving stanza with a from attribute',
|
||||||
|
() async {
|
||||||
|
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||||
|
|
||||||
|
// "Send" a stanza
|
||||||
|
final future = await awaiter.addPending(bareJid, 'abc123', 'iq');
|
||||||
|
|
||||||
|
// Receive the response.
|
||||||
|
final stanza =
|
||||||
|
XMLNode.fromString('<iq from="$bareJid" id="abc123" type="result" />');
|
||||||
|
await awaiter.onData(stanza);
|
||||||
|
expect(await future, stanza);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
|||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare().toString()}' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare()}' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@@ -77,7 +77,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
|||||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
),
|
),
|
||||||
StringExpectation(
|
StringExpectation(
|
||||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare().toString()}' xml:lang='en'>",
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='${settings.jid.domain}' from='${settings.jid.toBare()}' xml:lang='en'>",
|
||||||
'''
|
'''
|
||||||
<stream:stream
|
<stream:stream
|
||||||
xmlns="jabber:client"
|
xmlns="jabber:client"
|
||||||
@@ -89,6 +89,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
|||||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
<required/>
|
<required/>
|
||||||
</bind>
|
</bind>
|
||||||
|
<ver xmlns='urn:xmpp:features:rosterver'/>
|
||||||
</stream:features>
|
</stream:features>
|
||||||
''',
|
''',
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
53
packages/moxxmpp/test/roster_test.dart
Normal file
53
packages/moxxmpp/test/roster_test.dart
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'helpers/logging.dart';
|
||||||
|
import 'helpers/xmpp.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
initLogger();
|
||||||
|
|
||||||
|
test('Test a versioned roster fetch returning an empty iq', () async {
|
||||||
|
final sm = TestingRosterStateManager('ver14', []);
|
||||||
|
final rm = RosterManager(sm);
|
||||||
|
final cs = ConnectionSettings(
|
||||||
|
jid: JID.fromString('user@example.org'),
|
||||||
|
password: 'abc123',
|
||||||
|
);
|
||||||
|
final socket = StubTCPSocket([
|
||||||
|
...buildAuthenticatedPlay(cs),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" id="r1h3vzp7" type="get"><query xmlns="jabber:iq:roster" ver="ver14"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" id="r1h3vzp7" type="result" />',
|
||||||
|
ignoreId: true,
|
||||||
|
adjustId: true,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
socket,
|
||||||
|
)..connectionSettings = cs;
|
||||||
|
await conn.registerManagers([
|
||||||
|
rm,
|
||||||
|
PresenceManager(),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
RosterFeatureNegotiator(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
await conn.connect(
|
||||||
|
shouldReconnect: false,
|
||||||
|
waitUntilLogin: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Request the roster
|
||||||
|
final rawResult = await rm.requestRoster();
|
||||||
|
expect(rawResult.isType<RosterRequestResult>(), true);
|
||||||
|
final result = rawResult.get<RosterRequestResult>();
|
||||||
|
expect(result.items.isEmpty, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user