Compare commits
173 Commits
275d6e0346
...
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 | |||
| 327f695a40 | |||
| 8266765ff8 | |||
| e234c812ff | |||
| 4ff2992a03 | |||
| 09fd5845aa | |||
| 963f3f2cd9 | |||
| da1d28a6d6 | |||
| cbd90b1163 | |||
| f0538b0447 | |||
| 968604b0ba | |||
| 60279a84e0 | |||
| cf3287ccf4 | |||
| 1ab0ed856f | |||
| 10a5046431 | |||
| 4d76b9f57a | |||
| 0ec3777f44 | |||
| 6f5de9c4dc | |||
| 79d7e3ba64 | |||
| 8270185027 | |||
| 9e0f38154e | |||
| 1475cb542f | |||
| b949ec6ff5 | |||
| c3be199cca | |||
| 83ebe58c47 | |||
| 4db0ef6b34 | |||
| b95e75329d | |||
| 3163101f82 | |||
| bd4e1d28ea | |||
| b1da6e5a53 | |||
| c6552968d5 | |||
| 1d87c0ce95 | |||
| da591a552d | |||
| 47b679d168 | |||
| 320f4a8d4c | |||
| 1fdefacd52 | |||
| 09b8613c80 | |||
| f3d906e69b | |||
| 483cb0d7f1 | |||
| f73daf4d1c | |||
| a8693da262 | |||
| 0fb66f6aca | |||
| c9173c49bd | |||
| 2de1126fa9 | |||
| 4d312b2100 | |||
| c1ad646905 | |||
| c88ab940c4 | |||
| 916be1c927 | |||
| 5c47c35a46 | |||
| 7f3538875b | |||
| f6efa20ff4 | |||
| 8443411f07 | |||
| dc24b3c48a | |||
| f6abf3d5b5 | |||
| 63c84d9479 | |||
| 3e43ac22d7 | |||
| 47d821c02e | |||
| 355d580a9a | |||
| 03328bdf7a |
2
.gitlint
@@ -7,7 +7,7 @@ line-length=72
|
||||
[title-trailing-punctuation]
|
||||
[title-hard-tab]
|
||||
[title-match-regex]
|
||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$
|
||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all|flake|ci)+(,(meta|tests|style|docs|xep|core|example|all|flake|ci))*\)|release): [A-Z0-9].*$
|
||||
|
||||
|
||||
[body-trailing-whitespace]
|
||||
|
||||
@@ -1,28 +1,49 @@
|
||||
when:
|
||||
branch: master
|
||||
|
||||
pipeline:
|
||||
# Check moxxmpp
|
||||
moxxmpp-lint:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- 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
|
||||
|
||||
moxxmpp-test:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- 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
|
||||
|
||||
# Check moxxmpp_socket_tcp
|
||||
moxxmpp_socket_tcp-lint:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- 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
|
||||
|
||||
# moxxmpp-test:
|
||||
# image: dart:2.18.1
|
||||
# image: dart:3.0.7
|
||||
# commands:
|
||||
# - 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
|
||||
|
||||
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
|
||||
|
||||
15
README.md
@@ -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.
|
||||
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
if a DNS implementation is given, and supports StartTLS.
|
||||
|
||||
### moxxmpp_color
|
||||
|
||||
Implementation of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
|
||||
|
||||
## Development
|
||||
|
||||
To begin, use [melos](https://github.com/invertase/melos) to bootstrap the project: `melos bootstrap`. Then, the example
|
||||
@@ -24,6 +28,15 @@ To run the example, make sure that Flutter is correctly set up and working. If y
|
||||
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
|
||||
`ANDROID_AVD_HOME` are pointing to the correct directories.
|
||||
|
||||
## Examples
|
||||
|
||||
This repository contains 2 types of examples:
|
||||
|
||||
- `example_flutter`: An example of using moxxmpp using Flutter
|
||||
- `examples_dart`: A collection of pure Dart examples for showing different aspects of moxxmpp
|
||||
|
||||
For more information, see the respective README files.
|
||||
|
||||
## License
|
||||
|
||||
See `./LICENSE`.
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# melos_managed_dependency_overrides: moxxmpp,moxxmpp_socket_tcp
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -61,6 +61,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
final XmppConnection connection = XmppConnection(
|
||||
RandomBackoffReconnectionPolicy(1, 60),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
// The below causes the app to crash.
|
||||
//ExampleTcpSocketWrapper(),
|
||||
// In a production app, the below should be false.
|
||||
@@ -107,12 +108,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
connection.setConnectionSettings(
|
||||
ConnectionSettings(
|
||||
jid: JID.fromString(jidController.text),
|
||||
password: passwordController.text,
|
||||
useDirectTLS: true,
|
||||
),
|
||||
connection.connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString(jidController.text),
|
||||
password: passwordController.text,
|
||||
);
|
||||
final result = await connection.connect(waitUntilLogin: true);
|
||||
setState(() {
|
||||
@@ -16,10 +16,10 @@ dependencies:
|
||||
version: 0.1.4+1
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.6+1
|
||||
version: 0.3.1
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.2+9
|
||||
version: 0.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
6
examples_dart/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Files and directories created by pub.
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
||||
# Conventional directory for build output.
|
||||
build/
|
||||
7
examples_dart/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Dart Examples
|
||||
|
||||
Run using `dart run bin/<example>.dart`.
|
||||
|
||||
## Examples
|
||||
|
||||
- `component.dart`: Use moxxmpp to implement a component using XEP-0114.
|
||||
30
examples_dart/analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
94
examples_dart/bin/component.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class EchoMessageManager extends XmppManagerBase {
|
||||
EchoMessageManager() : super('org.moxxy.example.message');
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessage,
|
||||
priority: -100,
|
||||
xmlns: null,
|
||||
)
|
||||
];
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final body = stanza.firstTag('body');
|
||||
if (body == null) return state..done = true;
|
||||
|
||||
final bodyText = body.innerText();
|
||||
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.message(
|
||||
to: stanza.from,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'body',
|
||||
text: 'Hello, ${stanza.from}! You said "$bodyText"',
|
||||
),
|
||||
],
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
|
||||
return state..done = true;
|
||||
}
|
||||
}
|
||||
|
||||
void main(List<String> arguments) async {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ComponentToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('component.localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
);
|
||||
await conn.registerManagers([
|
||||
EchoMessageManager(),
|
||||
]);
|
||||
|
||||
final result = await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
if (result.isType<XmppError>()) {
|
||||
print('Failed to connect as component');
|
||||
return;
|
||||
}
|
||||
|
||||
// Just block for some time to test the connection
|
||||
await Future<void>.delayed(const Duration(seconds: 9999));
|
||||
}
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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!,
|
||||
];
|
||||
}
|
||||
}
|
||||
35
examples_dart/pubspec.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
name: example_dart
|
||||
description: A collection of samples for moxxmpp.
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
args: 2.4.1
|
||||
chalkdart: 2.0.9
|
||||
cli_repl: 0.2.3
|
||||
logging: ^1.0.2
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.4.0
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.4.0
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: ^0.5.1
|
||||
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
omemo_dart:
|
||||
git:
|
||||
url: https://github.com/PapaTutuWawa/omemo_dart.git
|
||||
rev: 49c7e114e6cf80dcde55fbbd218bba3182045862
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
126
flake.lock
generated
@@ -1,12 +1,74 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"android-nixpkgs": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678901627,
|
||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||
"lastModified": 1727554699,
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,27 +79,27 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1676076353,
|
||||
"narHash": "sha256-mdUtE8Tp40cZETwcq5tCwwLqkJVV1ULJQ5GKRtbshag=",
|
||||
"owner": "AtaraxiaSjel",
|
||||
"lastModified": 1727348695,
|
||||
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5deb99bdccbbb97e7562dee4ba8a3ee3021688e6",
|
||||
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "AtaraxiaSjel",
|
||||
"ref": "update/flutter",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1680273054,
|
||||
"narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=",
|
||||
"lastModified": 1727586919,
|
||||
"narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3",
|
||||
"rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -49,9 +111,39 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
||||
"android-nixpkgs": "android-nixpkgs",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
36
flake.nix
@@ -1,22 +1,20 @@
|
||||
{
|
||||
description = "moxxmpp";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
|
||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
android-nixpkgs.url = "github:tadfisher/android-nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
outputs = { self, nixpkgs, android-nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config = {
|
||||
android_sdk.accept_license = true;
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
unstable = import nixpkgs-unstable {
|
||||
inherit system;
|
||||
};
|
||||
# Everything to make Flutter happy
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
# TODO: Find a way to pin these
|
||||
#toolsVersion = "26.1.1";
|
||||
@@ -33,6 +31,7 @@
|
||||
useGoogleAPIs = false;
|
||||
useGoogleTVAddOns = false;
|
||||
};
|
||||
lib = pkgs.lib;
|
||||
pinnedJDK = pkgs.jdk17;
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
@@ -51,7 +50,7 @@
|
||||
};
|
||||
|
||||
devShell = let
|
||||
prosody-newer-community-modules = unstable.prosody.overrideAttrs (old: {
|
||||
prosody-newer-community-modules = pkgs.prosody.overrideAttrs (old: {
|
||||
communityModules = pkgs.fetchhg {
|
||||
url = "https://hg.prosody.im/prosody-modules";
|
||||
rev = "e3a3a6c86a9f";
|
||||
@@ -73,7 +72,7 @@
|
||||
buildInputs = with pkgs; [
|
||||
flutter pinnedJDK android.platform-tools dart # Dart
|
||||
gitlint # Code hygiene
|
||||
ripgrep # General utilities
|
||||
ripgrep # General utilities
|
||||
|
||||
# Flutter dependencies for Linux desktop
|
||||
atk
|
||||
@@ -103,7 +102,26 @@
|
||||
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 ];
|
||||
|
||||
ANDROID_SDK_ROOT = "${android.androidsdk}/share/android-sdk";
|
||||
ANDROID_HOME = "${android.androidsdk}/share/android-sdk";
|
||||
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
@@ -0,0 +1,4 @@
|
||||
set -ex
|
||||
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123
|
||||
@@ -52,5 +52,11 @@ log = {
|
||||
|
||||
pidfile = "/tmp/prosody.pid"
|
||||
|
||||
component_ports = { 8888 }
|
||||
component_interfaces = { '127.0.0.1' }
|
||||
VirtualHost "localhost"
|
||||
|
||||
Component "component.localhost"
|
||||
component_secret = "abc123"
|
||||
|
||||
Component "muc.localhost" "muc"
|
||||
|
||||
@@ -3,14 +3,16 @@ description: A sample command-line application.
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
logging: ^1.0.2
|
||||
moxxmpp: 0.2.0
|
||||
moxxmpp_socket_tcp: 0.2.1
|
||||
|
||||
logging: ^1.3.0
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
very_good_analysis: ^3.0.1
|
||||
build_runner: ^2.4.13
|
||||
test: ^1.25.8
|
||||
very_good_analysis: ^6.0.0
|
||||
|
||||
44
integration_tests/test/component_test.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
test('Test connecting to prosody as a component', () async {
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ComponentToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('component.localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
);
|
||||
|
||||
final result = await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
expect(result.isType<bool>(), true);
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
@@ -19,20 +21,18 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('Test authenticating against Prosody with SASL2, Bind2, and FAST', () async {
|
||||
test('Test authenticating against Prosody with SASL2, Bind2, and FAST',
|
||||
() async {
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..setConnectionSettings(
|
||||
ConnectionSettings(
|
||||
jid: JID.fromString('testuser@localhost'),
|
||||
password: 'abc123',
|
||||
useDirectTLS: false,
|
||||
|
||||
host: '127.0.0.1',
|
||||
port: 5222,
|
||||
),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('testuser1@localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 5222,
|
||||
);
|
||||
final csi = CSIManager();
|
||||
await csi.setInactive(sendNonza: false);
|
||||
@@ -42,17 +42,18 @@ void main() {
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(9, '', '', ScramHashType.sha1),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha256),
|
||||
ResourceBindingNegotiator(),
|
||||
FASTSaslNegotiator(),
|
||||
Bind2Negotiator(),
|
||||
StartTlsNegotiator(),
|
||||
Sasl2Negotiator(
|
||||
userAgent: const UserAgent(
|
||||
Sasl2Negotiator()
|
||||
..userAgent = const UserAgent(
|
||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||
software: 'moxxmpp',
|
||||
device: "PapaTutuWawa's awesome device",
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
final result = await conn.connect(
|
||||
@@ -61,7 +62,16 @@ void main() {
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
expect(result.isType<bool>(), true);
|
||||
expect(conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state, NegotiatorState.done);
|
||||
expect(conn.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!.fastToken != null, true,);
|
||||
expect(
|
||||
conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state,
|
||||
NegotiatorState.done,
|
||||
);
|
||||
expect(
|
||||
conn
|
||||
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
|
||||
.fastToken !=
|
||||
null,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
stdenv.mkDerivation {
|
||||
pname = "moxxmpp-docs";
|
||||
version = "0.2.0";
|
||||
version = "0.3.1";
|
||||
|
||||
PUB_CACHE = "${pubCache}";
|
||||
|
||||
|
||||
609
nix/moxxmpp.lock
@@ -1,28 +1,30 @@
|
||||
# GENERATED BY LOCK2NIX.py
|
||||
# DO NOT EDIT BY HAND
|
||||
{fetchzip, runCommand} : rec {
|
||||
_fe_analyzer_shared = fetchzip {
|
||||
sha256 = "1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5";
|
||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz";
|
||||
sha256 = "15fh9ka41dw4qsynv07msq4i243fibprcmafdygw5x88f7m55fq3";
|
||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/61.0.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
analyzer = fetchzip {
|
||||
sha256 = "0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r";
|
||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz";
|
||||
sha256 = "0w604zngxwfx0xqxvhbxrhdh04wgm6ad6a1lbwnyvmk57amv44np";
|
||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.13.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
args = fetchzip {
|
||||
sha256 = "0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g";
|
||||
url = "https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz";
|
||||
sha256 = "01ps253280c6dbx0vncw4wga4l2qp1zx779qjj2x06xzb3744zbz";
|
||||
url = "https://pub.dartlang.org/packages/args/versions/2.4.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
async = fetchzip {
|
||||
sha256 = "00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx";
|
||||
url = "https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz";
|
||||
sha256 = "0hfgvjajp5c2mw68186hgrk9v5zjhhi149hlhl0fap274p2v1g3q";
|
||||
url = "https://pub.dartlang.org/packages/async/versions/2.11.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -49,29 +51,29 @@
|
||||
};
|
||||
|
||||
build_daemon = fetchzip {
|
||||
sha256 = "0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j";
|
||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz";
|
||||
sha256 = "1wn7bq846vgdj62bkh9h25l95xdsndv0jdyw52nyr0591l3bpg3h";
|
||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_resolvers = fetchzip {
|
||||
sha256 = "0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5";
|
||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz";
|
||||
sha256 = "00h9abhrfmnl0xxziyf6p68sxnbv2ww1c4dhgpnz00mzbmamnq5c";
|
||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner = fetchzip {
|
||||
sha256 = "0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6";
|
||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz";
|
||||
sha256 = "0b5ha1l6k0gn2swqgqvfy2vl58klf81sxrjnmk0p7rj1wzbqjm7l";
|
||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner_core = fetchzip {
|
||||
sha256 = "0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z";
|
||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz";
|
||||
sha256 = "07r1kfy6ylm4i4xrb24ns8l26h4h1lgcskmnf8wvq2rd5d5hq790";
|
||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7%2B1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -84,29 +86,29 @@
|
||||
};
|
||||
|
||||
built_value = fetchzip {
|
||||
sha256 = "0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i";
|
||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz";
|
||||
sha256 = "1y84imf9xqqy3gnd5zz9bcln6mycy7qx35r70b0izm31ismlbzkv";
|
||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.6.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
checked_yaml = fetchzip {
|
||||
sha256 = "1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k";
|
||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz";
|
||||
sha256 = "1sn01yrmj0pkijn08g3v45c3zmyvdygk9svigkkzybgicdwlkpqs";
|
||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
code_builder = fetchzip {
|
||||
sha256 = "1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg";
|
||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz";
|
||||
sha256 = "1shgl7mxiyv0hhw326yqj2b9jxi1h74qxmsnxf1d1xc6yz766p9a";
|
||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.6.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
collection = fetchzip {
|
||||
sha256 = "1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545";
|
||||
url = "https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz";
|
||||
sha256 = "1mr8j0078c4z9hhckiq8m735rggsazwfprm0w9gisil51vh7j2mk";
|
||||
url = "https://pub.dartlang.org/packages/collection/versions/1.18.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -119,29 +121,29 @@
|
||||
};
|
||||
|
||||
coverage = fetchzip {
|
||||
sha256 = "0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d";
|
||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz";
|
||||
sha256 = "1yy9bgkax5b6kk7qa07p452v82fyj4rl1j03fn366ywyvhfrh6lp";
|
||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
crypto = fetchzip {
|
||||
sha256 = "1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds";
|
||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz";
|
||||
sha256 = "100ai8qa4p3dyvvd60c4xa9p0gm06yh0d68xgcfm3giraad8xmqj";
|
||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
cryptography = fetchzip {
|
||||
sha256 = "0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs";
|
||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz";
|
||||
sha256 = "1yxn9slqq93ri81fbr2nbsinz0mpk9wk39ny076ja8q31d4i8v3f";
|
||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.5.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
dart_style = fetchzip {
|
||||
sha256 = "01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v";
|
||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz";
|
||||
sha256 = "0cjhrb1hs8iw9smmfd0fgnxq3nm0w8sz17l6q6svyz6kif19wk9k";
|
||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -154,43 +156,29 @@
|
||||
};
|
||||
|
||||
fixnum = fetchzip {
|
||||
sha256 = "1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk";
|
||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.0.1.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";
|
||||
sha256 = "0nqrzj41ys8dpxf1x70r0kfj1avj0f2j2b7498k8kvc0i9c5asz7";
|
||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
frontend_server_client = fetchzip {
|
||||
sha256 = "0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj";
|
||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz";
|
||||
sha256 = "096v7ycix5hgnk750s1qgykyghl2mymhdkg39jrlk3kbj6xygq5b";
|
||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
glob = fetchzip {
|
||||
sha256 = "0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs";
|
||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz";
|
||||
sha256 = "0ffab3azx8zkma36mk6wnig8bn8g5g0vjrq2gl21y77rxgw9iqxj";
|
||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
graphs = fetchzip {
|
||||
sha256 = "0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9";
|
||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz";
|
||||
sha256 = "0fda0j8y6sq1rc9zpzglrzysl5h49y2ji1wq2lq0wx2c609dxm7f";
|
||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -217,78 +205,78 @@
|
||||
};
|
||||
|
||||
io = fetchzip {
|
||||
sha256 = "1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni";
|
||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz";
|
||||
sha256 = "101kd0rw26vglmr1m5p130kbrp3k7dk4p5nr77wsbwgg53w8c0d4";
|
||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
js = fetchzip {
|
||||
sha256 = "13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50";
|
||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz";
|
||||
sha256 = "124a9yqrjdw3p4nnirab9hm9ziwraldlw4q5cb3sr0dcrli74qpw";
|
||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.7.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_annotation = fetchzip {
|
||||
sha256 = "1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby";
|
||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz";
|
||||
sha256 = "1jjw7p8qyqajgdq4jqvxipq5w0qrq9dpi1qmia70pk995akryh6m";
|
||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.8.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_serializable = fetchzip {
|
||||
sha256 = "04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg";
|
||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz";
|
||||
sha256 = "1pmidql9x6s2pbhdx9x20pwqwvwpfkvrz0h0cm1f8pqis76c90hb";
|
||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.6.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
logging = fetchzip {
|
||||
sha256 = "0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs";
|
||||
url = "https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz";
|
||||
sha256 = "124hfjs66r30p92ndfmy5fymgy66yk9in97h8sq6fi7r78pqyc7g";
|
||||
url = "https://pub.dartlang.org/packages/logging/versions/1.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
matcher = fetchzip {
|
||||
sha256 = "0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0";
|
||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz";
|
||||
sha256 = "0inznqkrxqnq09lcbwvda3xd07qfm1k3aa6dv1wy39gvci8hybss";
|
||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.16.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
meta = fetchzip {
|
||||
sha256 = "01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5";
|
||||
url = "https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz";
|
||||
sha256 = "1l3zaz6q2s9mnm7s674xshsfqspy79p5kdbbnc99rf2l76avv4h3";
|
||||
url = "https://pub.dartlang.org/packages/meta/versions/1.9.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
mime = fetchzip {
|
||||
sha256 = "1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896";
|
||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz";
|
||||
sha256 = "1dha9z64bsz8xhi0p62vmlyikr8xwbdlrw90hxghmm3rdgd9h25w";
|
||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
moxlib = fetchzip {
|
||||
sha256 = "1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq";
|
||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz";
|
||||
sha256 = "1qaacmcqhq33grn2nq8sn23ki62dcmw0fqy589xm1zv6w0pzfmsk";
|
||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
node_preamble = fetchzip {
|
||||
sha256 = "0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx";
|
||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz";
|
||||
sha256 = "12ajg76r9aqmqkavvlxbnb3sszg1szcq3f30badkd0xc25mnhyh8";
|
||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
omemo_dart = fetchzip {
|
||||
sha256 = "09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi";
|
||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz";
|
||||
sha256 = "0fhf89ic5mdyld25l6rfb37a1fk1f0f2b4d72xi4r7pvr0ddjhz8";
|
||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.5.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -301,8 +289,8 @@
|
||||
};
|
||||
|
||||
path = fetchzip {
|
||||
sha256 = "16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181";
|
||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz";
|
||||
sha256 = "1mjdhq2fsz6i9krhp2mnaks2bcw34sa4p7mg0v6njk8dgx2754iv";
|
||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -315,8 +303,8 @@
|
||||
};
|
||||
|
||||
petitparser = fetchzip {
|
||||
sha256 = "1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0";
|
||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz";
|
||||
sha256 = "19zqrpb1z77aw1k2s8rsxdfxczzv9934g2rdfj2jyiv3pqgdq8gh";
|
||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -335,16 +323,30 @@
|
||||
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 {
|
||||
sha256 = "1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi";
|
||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz";
|
||||
sha256 = "0wpcfz1crxipbjm18m71pl4vl2ra8vw1n93ff8snr54mmlyfb9z1";
|
||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubspec_parse = fetchzip {
|
||||
sha256 = "19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1";
|
||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz";
|
||||
sha256 = "0dj8sf1w61g7vh1ly3sl690z0nwllzjzbapxswmgsglr0ndcyrs1";
|
||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -364,43 +366,43 @@
|
||||
};
|
||||
|
||||
shelf = fetchzip {
|
||||
sha256 = "0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1";
|
||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz";
|
||||
sha256 = "10yk98nadrgj5d3r3241kdaywjjs1j10mg8gacv80kg1mhcfdrxp";
|
||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_packages_handler = fetchzip {
|
||||
sha256 = "199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q";
|
||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz";
|
||||
sha256 = "1h8s42nff9ar0xn7yb42m64lpvmqzq8wranqrkkixdnp7w3pmv1x";
|
||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_static = fetchzip {
|
||||
sha256 = "1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34";
|
||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz";
|
||||
sha256 = "1bcqynn2z2syrigmrclxgg8hjhd1x9742938i62cicbaga6vclaz";
|
||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_web_socket = fetchzip {
|
||||
sha256 = "0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn";
|
||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz";
|
||||
sha256 = "110b5hrqwpnmq16shxxzjmcih5yfs5kh80dn8avfv0xj5iv7n94c";
|
||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_gen = fetchzip {
|
||||
sha256 = "1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz";
|
||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz";
|
||||
sha256 = "1jql5zccv4vnbbvwcpyyvz8l27pg1rviqbp4vrks5313nf4b0kjg";
|
||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_helper = fetchzip {
|
||||
sha256 = "044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd";
|
||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz";
|
||||
sha256 = "0mdd02vhcdcv9n58gzbx2q0bphwj0alz312ca1a8xpkf8jx3y8v4";
|
||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -413,29 +415,29 @@
|
||||
};
|
||||
|
||||
source_maps = fetchzip {
|
||||
sha256 = "18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6";
|
||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz";
|
||||
sha256 = "004lcfka01agxjdw7zjhrffdkisvgx5s61b5gsl8qqk2jd1rswa7";
|
||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.12.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_span = fetchzip {
|
||||
sha256 = "1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86";
|
||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz";
|
||||
sha256 = "1nybnf7l5chslp4fczhqnrgrhymy844lw7qrj6y08i626dshrd46";
|
||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.10.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stack_trace = fetchzip {
|
||||
sha256 = "0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na";
|
||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz";
|
||||
sha256 = "0xpk2cvmgdh46iwip9jsb54fqx13jnina8pk03akxkmsxvag5izb";
|
||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stream_channel = fetchzip {
|
||||
sha256 = "054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32";
|
||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz";
|
||||
sha256 = "0nrlw6zcscgnn6818krkbgs9qiv3f7q8pa7ljw1bqkrsb7xabm8s";
|
||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -455,8 +457,8 @@
|
||||
};
|
||||
|
||||
synchronized = fetchzip {
|
||||
sha256 = "1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i";
|
||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz";
|
||||
sha256 = "1fx1z1p5qkn4qnq24riw5s86vmq645ppg8f74iyv2fc9rvr301ar";
|
||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -469,36 +471,36 @@
|
||||
};
|
||||
|
||||
test = fetchzip {
|
||||
sha256 = "08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x";
|
||||
url = "https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz";
|
||||
sha256 = "002phlj2pg6nll5hv449izxbqk29zwmwc77d0jx2iimz18dgy2r5";
|
||||
url = "https://pub.dartlang.org/packages/test/versions/1.24.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_api = fetchzip {
|
||||
sha256 = "0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px";
|
||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz";
|
||||
sha256 = "0as1xcywjrd2zax3cm56qmnac12shf8c1ynnzzjwnggm23f61dxb";
|
||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.6.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_core = fetchzip {
|
||||
sha256 = "1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005";
|
||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz";
|
||||
sha256 = "1cx2rmz1xzk5z5yh8fpbsrsz4mgjanrw4xvnp0qzdnm2d7vhaq0y";
|
||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.5.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
timing = fetchzip {
|
||||
sha256 = "0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg";
|
||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz";
|
||||
sha256 = "15jvxsw7v0gwbdlykma60l1qlhlzb3brh6m0sg2bgbfir4l5s9gw";
|
||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
typed_data = fetchzip {
|
||||
sha256 = "1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g";
|
||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz";
|
||||
sha256 = "0q6ggc52vfpr8kqaq69h757wy942hvgshhnsr2pjdinb4zk2sxl1";
|
||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -511,8 +513,8 @@
|
||||
};
|
||||
|
||||
uuid = fetchzip {
|
||||
sha256 = "12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg";
|
||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz";
|
||||
sha256 = "1nh1hxfr6bhyadqqcxrpwrphmm75f1iq4rzfjdwa2486xwlh7vx3";
|
||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.7.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -525,8 +527,8 @@
|
||||
};
|
||||
|
||||
vm_service = fetchzip {
|
||||
sha256 = "05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5";
|
||||
url = "https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz";
|
||||
sha256 = "15ail7rbaq9ksg73cc0mw2k5imbiidl95yfd4v49k81gp5xmj92w";
|
||||
url = "https://pub.dartlang.org/packages/vm_service/versions/11.10.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -539,8 +541,8 @@
|
||||
};
|
||||
|
||||
web_socket_channel = fetchzip {
|
||||
sha256 = "147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a";
|
||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz";
|
||||
sha256 = "0f9441c4zifb5qadpjg319dcilimpkdhfacnkl543802bf8qjn4w";
|
||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -553,262 +555,262 @@
|
||||
};
|
||||
|
||||
xml = fetchzip {
|
||||
sha256 = "0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01";
|
||||
url = "https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz";
|
||||
sha256 = "120azx71gazvrrn07vd83vrffzrhsqnmf9rdjxl73rra9py8ixiy";
|
||||
url = "https://pub.dartlang.org/packages/xml/versions/6.3.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
yaml = fetchzip {
|
||||
sha256 = "0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw";
|
||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz";
|
||||
sha256 = "0awh9dynbhrlq8zgszaiyxbyyn9b6wyps1zww4z2lx62nbma0pda";
|
||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubCache = runCommand "moxxmpp-pub-cache" {} ''
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dartlang.org/_fe_analyzer_shared-50.0.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dev/_fe_analyzer_shared-61.0.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${analyzer} $out/hosted/pub.dartlang.org/analyzer-5.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${analyzer} $out/hosted/pub.dev/analyzer-5.13.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${args} $out/hosted/pub.dartlang.org/args-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${args} $out/hosted/pub.dev/args-2.4.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${async} $out/hosted/pub.dartlang.org/async-2.10.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${async} $out/hosted/pub.dev/async-2.11.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${boolean_selector} $out/hosted/pub.dartlang.org/boolean_selector-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${boolean_selector} $out/hosted/pub.dev/boolean_selector-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build} $out/hosted/pub.dartlang.org/build-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build} $out/hosted/pub.dev/build-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_config} $out/hosted/pub.dartlang.org/build_config-1.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_config} $out/hosted/pub.dev/build_config-1.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_daemon} $out/hosted/pub.dartlang.org/build_daemon-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_daemon} $out/hosted/pub.dev/build_daemon-3.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_resolvers} $out/hosted/pub.dartlang.org/build_resolvers-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_resolvers} $out/hosted/pub.dev/build_resolvers-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner} $out/hosted/pub.dartlang.org/build_runner-2.3.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_runner} $out/hosted/pub.dev/build_runner-2.3.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner_core} $out/hosted/pub.dartlang.org/build_runner_core-7.2.7
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_runner_core} $out/hosted/pub.dev/build_runner_core-7.2.7+1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_collection} $out/hosted/pub.dartlang.org/built_collection-5.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${built_collection} $out/hosted/pub.dev/built_collection-5.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_value} $out/hosted/pub.dartlang.org/built_value-8.4.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${built_value} $out/hosted/pub.dev/built_value-8.6.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${checked_yaml} $out/hosted/pub.dartlang.org/checked_yaml-2.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${checked_yaml} $out/hosted/pub.dev/checked_yaml-2.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${code_builder} $out/hosted/pub.dartlang.org/code_builder-4.3.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${code_builder} $out/hosted/pub.dev/code_builder-4.6.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${collection} $out/hosted/pub.dartlang.org/collection-1.17.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${collection} $out/hosted/pub.dev/collection-1.18.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${convert} $out/hosted/pub.dartlang.org/convert-3.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${convert} $out/hosted/pub.dev/convert-3.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${coverage} $out/hosted/pub.dartlang.org/coverage-1.6.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${coverage} $out/hosted/pub.dev/coverage-1.6.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${crypto} $out/hosted/pub.dartlang.org/crypto-3.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${crypto} $out/hosted/pub.dev/crypto-3.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${cryptography} $out/hosted/pub.dartlang.org/cryptography-2.0.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${cryptography} $out/hosted/pub.dev/cryptography-2.5.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${dart_style} $out/hosted/pub.dartlang.org/dart_style-2.2.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${dart_style} $out/hosted/pub.dev/dart_style-2.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${file} $out/hosted/pub.dartlang.org/file-6.1.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${file} $out/hosted/pub.dev/file-6.1.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${fixnum} $out/hosted/pub.dartlang.org/fixnum-1.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${fixnum} $out/hosted/pub.dev/fixnum-1.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed} $out/hosted/pub.dartlang.org/freezed-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${frontend_server_client} $out/hosted/pub.dev/frontend_server_client-3.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed_annotation} $out/hosted/pub.dartlang.org/freezed_annotation-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${glob} $out/hosted/pub.dev/glob-2.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${frontend_server_client} $out/hosted/pub.dartlang.org/frontend_server_client-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${graphs} $out/hosted/pub.dev/graphs-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${glob} $out/hosted/pub.dartlang.org/glob-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${hex} $out/hosted/pub.dev/hex-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${graphs} $out/hosted/pub.dartlang.org/graphs-2.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${http_multi_server} $out/hosted/pub.dev/http_multi_server-3.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${hex} $out/hosted/pub.dartlang.org/hex-0.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${http_parser} $out/hosted/pub.dev/http_parser-4.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_multi_server} $out/hosted/pub.dartlang.org/http_multi_server-3.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${io} $out/hosted/pub.dev/io-1.0.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_parser} $out/hosted/pub.dartlang.org/http_parser-4.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${js} $out/hosted/pub.dev/js-0.6.7
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${io} $out/hosted/pub.dartlang.org/io-1.0.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${json_annotation} $out/hosted/pub.dev/json_annotation-4.8.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${js} $out/hosted/pub.dartlang.org/js-0.6.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${json_serializable} $out/hosted/pub.dev/json_serializable-6.6.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_annotation} $out/hosted/pub.dartlang.org/json_annotation-4.7.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${logging} $out/hosted/pub.dev/logging-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_serializable} $out/hosted/pub.dartlang.org/json_serializable-6.5.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${matcher} $out/hosted/pub.dev/matcher-0.12.16
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${logging} $out/hosted/pub.dartlang.org/logging-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${meta} $out/hosted/pub.dev/meta-1.9.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${matcher} $out/hosted/pub.dartlang.org/matcher-0.12.13
|
||||
|
||||
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/pub.dev
|
||||
ln -s ${mime} $out/hosted/pub.dev/mime-1.0.4
|
||||
|
||||
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
|
||||
ln -s ${node_preamble} $out/hosted/pub.dartlang.org/node_preamble-2.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
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
|
||||
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
|
||||
ln -s ${package_config} $out/hosted/pub.dartlang.org/package_config-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${package_config} $out/hosted/pub.dev/package_config-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${path} $out/hosted/pub.dartlang.org/path-1.8.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${path} $out/hosted/pub.dev/path-1.8.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pedantic} $out/hosted/pub.dartlang.org/pedantic-1.11.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pedantic} $out/hosted/pub.dev/pedantic-1.11.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${petitparser} $out/hosted/pub.dartlang.org/petitparser-5.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${petitparser} $out/hosted/pub.dev/petitparser-5.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pinenacl} $out/hosted/pub.dartlang.org/pinenacl-0.5.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pinenacl} $out/hosted/pub.dev/pinenacl-0.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pool} $out/hosted/pub.dartlang.org/pool-1.5.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pool} $out/hosted/pub.dev/pool-1.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pub_semver} $out/hosted/pub.dartlang.org/pub_semver-2.1.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${protobuf} $out/hosted/pub.dev/protobuf-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pubspec_parse} $out/hosted/pub.dartlang.org/pubspec_parse-1.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${protoc_plugin} $out/hosted/pub.dev/protoc_plugin-20.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${random_string} $out/hosted/pub.dartlang.org/random_string-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pub_semver} $out/hosted/pub.dev/pub_semver-2.1.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${saslprep} $out/hosted/pub.dartlang.org/saslprep-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pubspec_parse} $out/hosted/pub.dev/pubspec_parse-1.2.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf} $out/hosted/pub.dartlang.org/shelf-1.4.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${random_string} $out/hosted/pub.dev/random_string-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dartlang.org/shelf_packages_handler-3.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${saslprep} $out/hosted/pub.dev/saslprep-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_static} $out/hosted/pub.dartlang.org/shelf_static-1.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf} $out/hosted/pub.dev/shelf-1.4.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_web_socket} $out/hosted/pub.dartlang.org/shelf_web_socket-1.0.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dev/shelf_packages_handler-3.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_gen} $out/hosted/pub.dartlang.org/source_gen-1.2.6
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_static} $out/hosted/pub.dev/shelf_static-1.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_helper} $out/hosted/pub.dartlang.org/source_helper-1.3.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_web_socket} $out/hosted/pub.dev/shelf_web_socket-1.0.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dartlang.org/source_map_stack_trace-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_gen} $out/hosted/pub.dev/source_gen-1.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_maps} $out/hosted/pub.dartlang.org/source_maps-0.10.11
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_helper} $out/hosted/pub.dev/source_helper-1.3.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_span} $out/hosted/pub.dartlang.org/source_span-1.9.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dev/source_map_stack_trace-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stack_trace} $out/hosted/pub.dartlang.org/stack_trace-1.11.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_maps} $out/hosted/pub.dev/source_maps-0.10.12
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_channel} $out/hosted/pub.dartlang.org/stream_channel-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_span} $out/hosted/pub.dev/source_span-1.10.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_transform} $out/hosted/pub.dartlang.org/stream_transform-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stack_trace} $out/hosted/pub.dev/stack_trace-1.11.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${string_scanner} $out/hosted/pub.dartlang.org/string_scanner-1.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stream_channel} $out/hosted/pub.dev/stream_channel-2.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${synchronized} $out/hosted/pub.dartlang.org/synchronized-3.0.0+2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stream_transform} $out/hosted/pub.dev/stream_transform-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${term_glyph} $out/hosted/pub.dartlang.org/term_glyph-1.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${string_scanner} $out/hosted/pub.dev/string_scanner-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test} $out/hosted/pub.dartlang.org/test-1.22.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${synchronized} $out/hosted/pub.dev/synchronized-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_api} $out/hosted/pub.dartlang.org/test_api-0.4.16
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${term_glyph} $out/hosted/pub.dev/term_glyph-1.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_core} $out/hosted/pub.dartlang.org/test_core-0.4.20
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test} $out/hosted/pub.dev/test-1.24.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${timing} $out/hosted/pub.dartlang.org/timing-1.0.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test_api} $out/hosted/pub.dev/test_api-0.6.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${typed_data} $out/hosted/pub.dartlang.org/typed_data-1.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test_core} $out/hosted/pub.dev/test_core-0.5.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${unorm_dart} $out/hosted/pub.dartlang.org/unorm_dart-0.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${timing} $out/hosted/pub.dev/timing-1.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${uuid} $out/hosted/pub.dartlang.org/uuid-3.0.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${typed_data} $out/hosted/pub.dev/typed_data-1.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${very_good_analysis} $out/hosted/pub.dartlang.org/very_good_analysis-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${unorm_dart} $out/hosted/pub.dev/unorm_dart-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${vm_service} $out/hosted/pub.dartlang.org/vm_service-9.4.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${uuid} $out/hosted/pub.dev/uuid-3.0.7
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${watcher} $out/hosted/pub.dartlang.org/watcher-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${very_good_analysis} $out/hosted/pub.dev/very_good_analysis-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${web_socket_channel} $out/hosted/pub.dartlang.org/web_socket_channel-2.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${vm_service} $out/hosted/pub.dev/vm_service-11.10.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dartlang.org/webkit_inspection_protocol-1.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${watcher} $out/hosted/pub.dev/watcher-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${xml} $out/hosted/pub.dartlang.org/xml-6.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${web_socket_channel} $out/hosted/pub.dev/web_socket_channel-2.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${yaml} $out/hosted/pub.dartlang.org/yaml-3.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
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,39 @@
|
||||
## 0.4.1
|
||||
- Moved FAST from staging to xep_0484.dart
|
||||
|
||||
## 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**: Changed order of parameters of `CryptographicHashManager.hashFromData`
|
||||
- **BREAKING**: Removed support for XEP-0414, as the (supported) hash computations are already implemented by `CryptographicHashManager.hashFromData`.
|
||||
- The `DiscoManager` now only handled entity capabilities if a `EntityCapabilityManager` is registered.
|
||||
- The `EntityCapabilityManager` now verifies and validates its data before caching.
|
||||
- **BREAKING**: Added the `resumed` parameter to `StreamNegotiationsDoneEvent`. Use this to check if the current stream is new or resumed instead of using the `ConnectionStateChangedEvent`.
|
||||
- **BREAKING**: Remove `DiscoManager.discoInfoCapHashQuery`.
|
||||
- **BREAKING**: The entity argument of `DiscoManager.discoInfoQuery` and `DiscoManager.discoItemsQuery` are now `JID` instead of `String`.
|
||||
- **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`.
|
||||
- **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument.
|
||||
- Sent stanzas are now kept in a queue until sent.
|
||||
- **BREAKING**: `MessageManager.sendMessage` does not use `MessageDetails` anymore. Instead, use `TypedMap`.
|
||||
- `MessageManager` now allows registering callbacks for adding data whenever a message is sent.
|
||||
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
|
||||
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
|
||||
- **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
|
||||
|
||||
- Fix some issues with running moxxmpp as a component
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- **BREAKING**: Removed `connectAwaitable` and merged it with `connect`.
|
||||
@@ -6,6 +42,9 @@
|
||||
- Renamed `ResourceBindingSuccessEvent` to `ResourceBoundEvent`
|
||||
- **BREAKING**: Removed `isFeatureSupported` from the manager attributes. The managers now all have a method `isFeatureSupported` that works the same
|
||||
- The `PresenceManager` is now optional
|
||||
- **BREAKING**: Removed `setConnectionSettings` and `getConnectionSettings`. Just directly acces the `connectionSettings` field.
|
||||
- Implement XEP-0114 for implementing components
|
||||
- **BREAKING**: Remove `useDirectTLS` from `ConnectionSettings`
|
||||
|
||||
## 0.1.6+1
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Include the following as a dependency in your pubspec file:
|
||||
```
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.2.0
|
||||
version: 0.3.1
|
||||
```
|
||||
|
||||
You can find the documentation [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||
|
||||
@@ -5,6 +5,9 @@ export 'package:moxxmpp/src/connection_errors.dart';
|
||||
export 'package:moxxmpp/src/connectivity.dart';
|
||||
export 'package:moxxmpp/src/errors.dart';
|
||||
export 'package:moxxmpp/src/events.dart';
|
||||
export 'package:moxxmpp/src/handlers/base.dart';
|
||||
export 'package:moxxmpp/src/handlers/client.dart';
|
||||
export 'package:moxxmpp/src/handlers/component.dart';
|
||||
export 'package:moxxmpp/src/iq.dart';
|
||||
export 'package:moxxmpp/src/jid.dart';
|
||||
export 'package:moxxmpp/src/managers/attributes.dart';
|
||||
@@ -15,7 +18,6 @@ export 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
export 'package:moxxmpp/src/managers/priorities.dart';
|
||||
export 'package:moxxmpp/src/message.dart';
|
||||
export 'package:moxxmpp/src/namespaces.dart';
|
||||
export 'package:moxxmpp/src/negotiators/manager.dart';
|
||||
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
export 'package:moxxmpp/src/ping.dart';
|
||||
@@ -36,15 +38,17 @@ export 'package:moxxmpp/src/settings.dart';
|
||||
export 'package:moxxmpp/src/socket.dart';
|
||||
export 'package:moxxmpp/src/stanza.dart';
|
||||
export 'package:moxxmpp/src/stringxml.dart';
|
||||
export 'package:moxxmpp/src/types/result.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||
export 'package:moxxmpp/src/util/typed_map.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_0030/errors.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/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_0060/errors.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0060/helpers.dart';
|
||||
@@ -60,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/xep_0198.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_0297.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||
@@ -82,7 +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/user_agent.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0414.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0421.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||
@@ -90,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_0449.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 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// A surrogate key for awaiting stanzas.
|
||||
@immutable
|
||||
class _StanzaSurrogateKey {
|
||||
const _StanzaSurrogateKey(this.sentTo, this.id, this.tag);
|
||||
/// (JID we sent a stanza to, the id of the sent stanza, the tag of the sent stanza).
|
||||
// ignore: avoid_private_typedef_functions
|
||||
typedef _StanzaCompositeKey = (String?, String, String);
|
||||
|
||||
/// The JID the original stanza was sent to. We expect the result to come from the
|
||||
/// same JID.
|
||||
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;
|
||||
}
|
||||
}
|
||||
/// Callback function that returns the bare JID of the connection as a String.
|
||||
typedef GetBareJidCallback = String Function();
|
||||
|
||||
/// 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.
|
||||
@@ -40,8 +18,12 @@ class _StanzaSurrogateKey {
|
||||
///
|
||||
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
|
||||
class StanzaAwaiter {
|
||||
StanzaAwaiter(this._bareJidCallback);
|
||||
|
||||
final GetBareJidCallback _bareJidCallback;
|
||||
|
||||
/// 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].
|
||||
final Lock _lock = Lock();
|
||||
@@ -52,30 +34,33 @@ class StanzaAwaiter {
|
||||
/// [tag] is the stanza's tag name.
|
||||
///
|
||||
/// 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 = Completer<XMLNode>();
|
||||
_pending[_StanzaSurrogateKey(to, id, tag)] = completer;
|
||||
_pending[(processedTo, id, tag)] = completer;
|
||||
return completer;
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Checks if the stanza [stanza] is being awaited. [bareJid] is the bare JID of
|
||||
/// the connection.
|
||||
/// Checks if the stanza [stanza] is being awaited.
|
||||
/// If [stanza] is awaited, resolves the future and returns true. If not, returns
|
||||
/// false.
|
||||
Future<bool> onData(XMLNode stanza, JID bareJid) async {
|
||||
assert(bareJid.isBare(), 'bareJid must be bare');
|
||||
|
||||
Future<bool> onData(XMLNode stanza) async {
|
||||
final id = stanza.attributes['id'] as String?;
|
||||
if (id == null) return false;
|
||||
|
||||
final key = _StanzaSurrogateKey(
|
||||
// Section 8.1.2.1 § 3 of RFC 6120 says that an empty "from" indicates that the
|
||||
// attribute is implicitly from our own bare JID.
|
||||
stanza.attributes['from'] as String? ?? bareJid.toString(),
|
||||
// Check if we want to send a stanza to our bare JID and replace it with null.
|
||||
final from = stanza.attributes['from'] as String?;
|
||||
final processedFrom =
|
||||
from != null && from == _bareJidCallback() ? null : from;
|
||||
|
||||
final key = (
|
||||
processedFrom,
|
||||
id,
|
||||
stanza.tag,
|
||||
);
|
||||
@@ -91,4 +76,19 @@ class StanzaAwaiter {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
import 'package:xml/xml.dart';
|
||||
import 'package:xml/xml_events.dart';
|
||||
|
||||
class XmlStreamBuffer extends StreamTransformerBase<String, XMLNode> {
|
||||
XmlStreamBuffer()
|
||||
: _streamController = StreamController(),
|
||||
_decoder = const XmlNodeDecoder();
|
||||
final StreamController<XMLNode> _streamController;
|
||||
final XmlNodeDecoder _decoder;
|
||||
|
||||
@override
|
||||
Stream<XMLNode> bind(Stream<String> stream) {
|
||||
stream
|
||||
.toXmlEvents()
|
||||
.selectSubtreeEvents((event) {
|
||||
return event.qualifiedName != 'stream:stream';
|
||||
})
|
||||
.transform(_decoder)
|
||||
.listen((nodes) {
|
||||
for (final node in nodes) {
|
||||
if (node.nodeType == XmlNodeType.ELEMENT) {
|
||||
_streamController.add(XMLNode.fromXmlElement(node as XmlElement));
|
||||
}
|
||||
}
|
||||
});
|
||||
return _streamController.stream;
|
||||
}
|
||||
}
|
||||
@@ -46,3 +46,15 @@ class NoAuthenticatorAvailableError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Returned by the negotiation handler if unexpected data has been received
|
||||
class UnexpectedDataError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Returned by the ComponentToServerNegotiator if the handshake is not successful.
|
||||
class InvalidHandshakeCredentialsError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,6 @@ abstract class XmppError {
|
||||
bool isRecoverable();
|
||||
}
|
||||
|
||||
/// Returned if we could not establish a TCP connection
|
||||
/// to the server.
|
||||
class NoConnectionError extends XmppError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if a socket error occured
|
||||
class SocketError extends XmppError {
|
||||
SocketError(this.event);
|
||||
|
||||
@@ -4,28 +4,20 @@ import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/roster/roster.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0084.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||
|
||||
abstract class XmppEvent {}
|
||||
|
||||
/// Triggered when the connection state of the XmppConnection has
|
||||
/// changed.
|
||||
class ConnectionStateChangedEvent extends XmppEvent {
|
||||
ConnectionStateChangedEvent(this.state, this.before, this.resumed);
|
||||
ConnectionStateChangedEvent(this.state, this.before);
|
||||
final XmppConnectionState before;
|
||||
final XmppConnectionState state;
|
||||
final bool resumed;
|
||||
|
||||
/// Indicates whether the connection state switched from a not connected state to a
|
||||
/// connected state.
|
||||
@@ -75,58 +67,42 @@ class RosterUpdatedEvent extends XmppEvent {
|
||||
|
||||
/// Triggered when a message is received
|
||||
class MessageEvent extends XmppEvent {
|
||||
MessageEvent({
|
||||
required this.body,
|
||||
required this.fromJid,
|
||||
required this.toJid,
|
||||
required this.sid,
|
||||
required this.stanzaId,
|
||||
required this.isCarbon,
|
||||
required this.deliveryReceiptRequested,
|
||||
required this.isMarkable,
|
||||
required this.encrypted,
|
||||
required this.other,
|
||||
this.error,
|
||||
MessageEvent(
|
||||
this.from,
|
||||
this.to,
|
||||
this.encrypted,
|
||||
this.extensions, {
|
||||
this.id,
|
||||
this.type,
|
||||
this.oob,
|
||||
this.sfs,
|
||||
this.sims,
|
||||
this.reply,
|
||||
this.chatState,
|
||||
this.fun,
|
||||
this.funReplacement,
|
||||
this.funCancellation,
|
||||
this.messageRetraction,
|
||||
this.messageCorrectionId,
|
||||
this.messageReactions,
|
||||
this.messageProcessingHints,
|
||||
this.stickerPackId,
|
||||
this.error,
|
||||
this.encryptionError,
|
||||
});
|
||||
final StanzaError? error;
|
||||
final String body;
|
||||
final JID fromJid;
|
||||
final JID toJid;
|
||||
final String sid;
|
||||
|
||||
/// The from attribute of the message.
|
||||
final JID from;
|
||||
|
||||
/// The to attribute of the message.
|
||||
final JID to;
|
||||
|
||||
/// The id attribute of the message.
|
||||
final String? id;
|
||||
|
||||
/// The type attribute of the message.
|
||||
final String? type;
|
||||
final StableStanzaId stanzaId;
|
||||
final bool isCarbon;
|
||||
final bool deliveryReceiptRequested;
|
||||
final bool isMarkable;
|
||||
final OOBData? oob;
|
||||
final StatelessFileSharingData? sfs;
|
||||
final StatelessMediaSharingData? sims;
|
||||
final ReplyData? reply;
|
||||
final ChatState? chatState;
|
||||
final FileMetadataData? fun;
|
||||
final String? funReplacement;
|
||||
final String? funCancellation;
|
||||
|
||||
final StanzaError? error;
|
||||
|
||||
/// Flag indicating whether the message was encrypted.
|
||||
final bool encrypted;
|
||||
final MessageRetractionData? messageRetraction;
|
||||
final String? messageCorrectionId;
|
||||
final MessageReactions? messageReactions;
|
||||
final List<MessageProcessingHint>? messageProcessingHints;
|
||||
final String? stickerPackId;
|
||||
final Map<String, dynamic> other;
|
||||
|
||||
/// The error in case an encryption error occurred.
|
||||
final Object? encryptionError;
|
||||
|
||||
/// Data added by other handlers.
|
||||
final TypedMap<StanzaHandlerExtension> extensions;
|
||||
|
||||
/// Shorthand for extensions.get<T>().
|
||||
T? get<T>() => extensions.get<T>();
|
||||
}
|
||||
|
||||
/// Triggered when a client responds to our delivery receipt request
|
||||
@@ -137,13 +113,19 @@ class DeliveryReceiptReceivedEvent extends XmppEvent {
|
||||
}
|
||||
|
||||
class ChatMarkerEvent extends XmppEvent {
|
||||
ChatMarkerEvent({
|
||||
required this.type,
|
||||
required this.from,
|
||||
required this.id,
|
||||
});
|
||||
ChatMarkerEvent(
|
||||
this.from,
|
||||
this.type,
|
||||
this.id,
|
||||
);
|
||||
|
||||
/// The entity that sent the chat marker.
|
||||
final JID from;
|
||||
final String type;
|
||||
|
||||
/// The type of chat marker that was sent.
|
||||
final ChatMarker type;
|
||||
|
||||
/// The id of the message that the marker applies to.
|
||||
final String id;
|
||||
}
|
||||
|
||||
@@ -167,13 +149,6 @@ class ResourceBoundEvent extends XmppEvent {
|
||||
final String resource;
|
||||
}
|
||||
|
||||
/// Triggered when we receive presence
|
||||
class PresenceReceivedEvent extends XmppEvent {
|
||||
PresenceReceivedEvent(this.jid, this.presence);
|
||||
final JID jid;
|
||||
final Stanza presence;
|
||||
}
|
||||
|
||||
/// Triggered when we are starting an connection attempt
|
||||
class ConnectingEvent extends XmppEvent {}
|
||||
|
||||
@@ -191,15 +166,31 @@ class SubscriptionRequestReceivedEvent extends XmppEvent {
|
||||
final JID from;
|
||||
}
|
||||
|
||||
/// Triggered when we receive a new or updated avatar
|
||||
class AvatarUpdatedEvent extends XmppEvent {
|
||||
AvatarUpdatedEvent({
|
||||
required this.jid,
|
||||
required this.base64,
|
||||
required this.hash,
|
||||
});
|
||||
final String jid;
|
||||
final String base64;
|
||||
/// Triggered when we receive a new or updated avatar via XEP-0084
|
||||
class UserAvatarUpdatedEvent extends XmppEvent {
|
||||
UserAvatarUpdatedEvent(
|
||||
this.jid,
|
||||
this.metadata,
|
||||
);
|
||||
|
||||
/// The JID of the user updating their avatar.
|
||||
final JID jid;
|
||||
|
||||
/// The metadata of the avatar.
|
||||
final List<UserAvatarMetadata> metadata;
|
||||
}
|
||||
|
||||
/// Triggered when we receive a new or updated avatar via XEP-0054
|
||||
class VCardAvatarUpdatedEvent extends XmppEvent {
|
||||
VCardAvatarUpdatedEvent(
|
||||
this.jid,
|
||||
this.hash,
|
||||
);
|
||||
|
||||
/// The JID of the entity that updated their avatar.
|
||||
final JID jid;
|
||||
|
||||
/// The SHA-1 hash of the avatar.
|
||||
final String hash;
|
||||
}
|
||||
|
||||
@@ -257,4 +248,10 @@ class NonRecoverableErrorEvent extends XmppEvent {
|
||||
}
|
||||
|
||||
/// Triggered when the stream negotiations are done.
|
||||
class StreamNegotiationsDoneEvent extends XmppEvent {}
|
||||
class StreamNegotiationsDoneEvent extends XmppEvent {
|
||||
StreamNegotiationsDoneEvent(this.resumed);
|
||||
|
||||
/// Flag indicating whether we resumed a previous stream (true) or are in a completely
|
||||
/// new stream (false).
|
||||
final bool resumed;
|
||||
}
|
||||
|
||||
132
packages/moxxmpp/lib/src/handlers/base.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/parser.dart';
|
||||
import 'package:moxxmpp/src/settings.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// A callback for when the [NegotiationsHandler] is done.
|
||||
typedef NegotiationsDoneCallback = Future<void> Function();
|
||||
|
||||
/// A callback for the case that an error occurs while negotiating.
|
||||
typedef ErrorCallback = Future<void> Function(XmppError);
|
||||
|
||||
/// Return true if the current connection is authenticated. If not, return false.
|
||||
typedef IsAuthenticatedFunction = bool Function();
|
||||
|
||||
/// Send a nonza on the stream
|
||||
typedef SendNonzaFunction = void Function(XMLNode);
|
||||
|
||||
/// Returns the connection settings.
|
||||
typedef GetConnectionSettingsFunction = ConnectionSettings Function();
|
||||
|
||||
/// Resets the stream parser's state.
|
||||
typedef ResetStreamParserFunction = void Function();
|
||||
|
||||
/// This class implements the stream feature negotiation for XmppConnection.
|
||||
abstract class NegotiationsHandler {
|
||||
@protected
|
||||
late final Logger log;
|
||||
|
||||
/// Map of all negotiators registered against the handler.
|
||||
@protected
|
||||
final Map<String, XmppFeatureNegotiatorBase> negotiators = {};
|
||||
|
||||
/// Function that is called once the negotiator is done with its stream negotiations.
|
||||
@protected
|
||||
late final NegotiationsDoneCallback onNegotiationsDone;
|
||||
|
||||
/// XmppConnection's handleError method.
|
||||
@protected
|
||||
late final ErrorCallback handleError;
|
||||
|
||||
/// Returns true if the connection is authenticated. If not, returns false.
|
||||
@protected
|
||||
late final IsAuthenticatedFunction isAuthenticated;
|
||||
|
||||
/// Send a nonza over the stream.
|
||||
@protected
|
||||
late final SendNonzaFunction sendNonza;
|
||||
|
||||
/// Get the connection's settings.
|
||||
@protected
|
||||
late final GetConnectionSettingsFunction getConnectionSettings;
|
||||
|
||||
@protected
|
||||
late final ResetStreamParserFunction resetStreamParser;
|
||||
|
||||
/// The id included in the last stream header.
|
||||
@protected
|
||||
String? streamId;
|
||||
|
||||
/// Set the id of the last stream header.
|
||||
void setStreamHeaderId(String? id) {
|
||||
streamId = id;
|
||||
}
|
||||
|
||||
/// Returns, if registered, a negotiator with id [id].
|
||||
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
|
||||
negotiators[id] as T?;
|
||||
|
||||
/// Register the parameters as the corresponding methods in this class. Also
|
||||
/// initializes the logger.
|
||||
void register(
|
||||
NegotiationsDoneCallback onNegotiationsDone,
|
||||
ErrorCallback handleError,
|
||||
IsAuthenticatedFunction isAuthenticated,
|
||||
SendNonzaFunction sendNonza,
|
||||
GetConnectionSettingsFunction getConnectionSettings,
|
||||
ResetStreamParserFunction resetStreamParser,
|
||||
) {
|
||||
this.onNegotiationsDone = onNegotiationsDone;
|
||||
this.handleError = handleError;
|
||||
this.isAuthenticated = isAuthenticated;
|
||||
this.sendNonza = sendNonza;
|
||||
this.getConnectionSettings = getConnectionSettings;
|
||||
this.resetStreamParser = resetStreamParser;
|
||||
log = Logger(toString());
|
||||
}
|
||||
|
||||
/// Returns the xmlns attribute that stanzas should have.
|
||||
String getStanzaNamespace();
|
||||
|
||||
/// Registers the negotiator [negotiator] against this negotiations handler.
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
|
||||
|
||||
/// Sends the stream header.
|
||||
void sendStreamHeader();
|
||||
|
||||
/// Runs the post-register callback of all negotiators.
|
||||
Future<void> runPostRegisterCallback() async {
|
||||
for (final negotiator in negotiators.values) {
|
||||
await negotiator.postRegisterCallback();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendEventToNegotiators(XmppEvent event) async {
|
||||
for (final negotiator in negotiators.values) {
|
||||
await negotiator.onXmppEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove [feature] from the stream features we are currently negotiating.
|
||||
void removeNegotiatingFeature(String feature) {}
|
||||
|
||||
/// Resets all registered negotiators and the negotiation handler.
|
||||
@mustCallSuper
|
||||
void reset() {
|
||||
streamId = null;
|
||||
for (final negotiator in negotiators.values) {
|
||||
negotiator.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// Called whenever the stream buffer outputs a new event [event].
|
||||
Future<void> negotiate(XMPPStreamObject event) async {
|
||||
if (event is XMPPStreamHeader) {
|
||||
streamId = event.attributes['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
227
packages/moxxmpp/lib/src/handlers/client.dart
Normal file
@@ -0,0 +1,227 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/connection_errors.dart';
|
||||
import 'package:moxxmpp/src/handlers/base.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/parser.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// "Nonza" describing the XMPP stream header of a client-to-server connection.
|
||||
class ClientStreamHeaderNonza extends XMLNode {
|
||||
ClientStreamHeaderNonza(JID jid)
|
||||
: super(
|
||||
tag: 'stream:stream',
|
||||
attributes: <String, String>{
|
||||
'xmlns': stanzaXmlns,
|
||||
'version': '1.0',
|
||||
'xmlns:stream': streamXmlns,
|
||||
'to': jid.domain,
|
||||
'from': jid.toBare().toString(),
|
||||
'xml:lang': 'en',
|
||||
},
|
||||
closeTag: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// This class implements the stream feature negotiation for usage in client to server
|
||||
/// connections.
|
||||
class ClientToServerNegotiator extends NegotiationsHandler {
|
||||
ClientToServerNegotiator() : super();
|
||||
|
||||
/// Cached list of stream features.
|
||||
final List<XMLNode> _streamFeatures = List.empty(growable: true);
|
||||
|
||||
/// The currently active negotiator.
|
||||
XmppFeatureNegotiatorBase? _currentNegotiator;
|
||||
|
||||
@override
|
||||
String getStanzaNamespace() => stanzaXmlns;
|
||||
|
||||
@override
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
|
||||
negotiators[negotiator.id] = negotiator;
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
super.reset();
|
||||
|
||||
// Prevent leaking the last active negotiator
|
||||
_currentNegotiator = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeNegotiatingFeature(String feature) {
|
||||
_streamFeatures.removeWhere((node) {
|
||||
return node.attributes['xmlns'] == feature;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void sendStreamHeader() {
|
||||
resetStreamParser();
|
||||
sendNonza(
|
||||
XMLNode(
|
||||
tag: 'xml',
|
||||
attributes: {'version': '1.0'},
|
||||
closeTag: false,
|
||||
isDeclaration: true,
|
||||
children: [
|
||||
ClientStreamHeaderNonza(getConnectionSettings().jid),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns true if all mandatory features in [features] have been negotiated.
|
||||
/// Otherwise returns false.
|
||||
bool _isMandatoryNegotiationDone(List<XMLNode> features) {
|
||||
return features.every((XMLNode feature) {
|
||||
return feature.firstTag('required') == null &&
|
||||
feature.tag != 'mechanisms';
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if we can still negotiate. Returns false if no negotiator is
|
||||
/// matching and ready.
|
||||
bool _isNegotiationPossible(List<XMLNode> features) {
|
||||
return getNextNegotiator(features, log: false) != null;
|
||||
}
|
||||
|
||||
/// Returns the next negotiator that matches [features]. Returns null if none can be
|
||||
/// picked. If [log] is true, then the list of matching negotiators will be logged.
|
||||
@visibleForTesting
|
||||
XmppFeatureNegotiatorBase? getNextNegotiator(
|
||||
List<XMLNode> features, {
|
||||
bool log = true,
|
||||
}) {
|
||||
final matchingNegotiators =
|
||||
negotiators.values.where((XmppFeatureNegotiatorBase negotiator) {
|
||||
return negotiator.state == NegotiatorState.ready &&
|
||||
negotiator.matchesFeature(features);
|
||||
}).toList()
|
||||
..sort((a, b) => b.priority.compareTo(a.priority));
|
||||
|
||||
if (log) {
|
||||
this.log.finest(
|
||||
'List of matching negotiators: ${matchingNegotiators.map((a) => a.id)}',
|
||||
);
|
||||
}
|
||||
|
||||
if (matchingNegotiators.isEmpty) return null;
|
||||
|
||||
return matchingNegotiators.first;
|
||||
}
|
||||
|
||||
Future<void> _executeCurrentNegotiator(XMLNode nonza) async {
|
||||
// If we don't have a negotiator, get one
|
||||
_currentNegotiator ??= getNextNegotiator(_streamFeatures);
|
||||
if (_currentNegotiator == null &&
|
||||
_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||
!_isNegotiationPossible(_streamFeatures)) {
|
||||
log.finest('Negotiations done!');
|
||||
await onNegotiationsDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a next negotiator, we have to bail
|
||||
if (_currentNegotiator == null &&
|
||||
!_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||
!_isNegotiationPossible(_streamFeatures)) {
|
||||
// We failed before authenticating
|
||||
if (!isAuthenticated()) {
|
||||
log.severe('No negotiator could be picked while unauthenticated');
|
||||
await handleError(NoMatchingAuthenticationMechanismAvailableError());
|
||||
return;
|
||||
} else {
|
||||
log.severe(
|
||||
'No negotiator could be picked while negotiations are not done',
|
||||
);
|
||||
await handleError(NoAuthenticatorAvailableError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final result = await _currentNegotiator!.negotiate(nonza);
|
||||
if (result.isType<NegotiatorError>()) {
|
||||
log.severe('Negotiator returned an error');
|
||||
await handleError(result.get<NegotiatorError>());
|
||||
return;
|
||||
}
|
||||
|
||||
final state = result.get<NegotiatorState>();
|
||||
_currentNegotiator!.state = state;
|
||||
switch (state) {
|
||||
case NegotiatorState.ready:
|
||||
return;
|
||||
case NegotiatorState.done:
|
||||
if (_currentNegotiator!.sendStreamHeaderWhenDone) {
|
||||
_currentNegotiator = null;
|
||||
_streamFeatures.clear();
|
||||
sendStreamHeader();
|
||||
} else {
|
||||
removeNegotiatingFeature(_currentNegotiator!.negotiatingXmlns);
|
||||
_currentNegotiator = null;
|
||||
|
||||
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||
!_isNegotiationPossible(_streamFeatures)) {
|
||||
log.finest('Negotiations done!');
|
||||
await onNegotiationsDone();
|
||||
} else {
|
||||
_currentNegotiator = getNextNegotiator(_streamFeatures);
|
||||
log.finest('Chose ${_currentNegotiator!.id} as next negotiator');
|
||||
|
||||
final fakeStanza = XMLNode(
|
||||
tag: 'stream:features',
|
||||
children: _streamFeatures,
|
||||
);
|
||||
|
||||
await _executeCurrentNegotiator(fakeStanza);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NegotiatorState.retryLater:
|
||||
log.finest('Negotiator wants to continue later. Picking new one...');
|
||||
_currentNegotiator!.state = NegotiatorState.ready;
|
||||
|
||||
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||
!_isNegotiationPossible(_streamFeatures)) {
|
||||
log.finest('Negotiations done!');
|
||||
|
||||
await onNegotiationsDone();
|
||||
} else {
|
||||
log.finest('Picking new negotiator...');
|
||||
_currentNegotiator = getNextNegotiator(_streamFeatures);
|
||||
log.finest('Chose $_currentNegotiator as next negotiator');
|
||||
final fakeStanza = XMLNode(
|
||||
tag: 'stream:features',
|
||||
children: _streamFeatures,
|
||||
);
|
||||
await _executeCurrentNegotiator(fakeStanza);
|
||||
}
|
||||
break;
|
||||
case NegotiatorState.skipRest:
|
||||
log.finest(
|
||||
'Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!',
|
||||
);
|
||||
|
||||
await onNegotiationsDone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> negotiate(XMPPStreamObject event) async {
|
||||
if (event is XMPPStreamElement) {
|
||||
if (event.node.tag == 'stream:features') {
|
||||
// Store the received stream features
|
||||
_streamFeatures
|
||||
..clear()
|
||||
..addAll(event.node.children);
|
||||
}
|
||||
|
||||
await _executeCurrentNegotiator(event.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
packages/moxxmpp/lib/src/handlers/component.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:moxxmpp/src/connection_errors.dart';
|
||||
import 'package:moxxmpp/src/handlers/base.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/parser.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// Nonza describing the XMPP stream header.
|
||||
class ComponentStreamHeaderNonza extends XMLNode {
|
||||
ComponentStreamHeaderNonza(JID jid)
|
||||
: assert(jid.isBare(), 'Component JID must be bare'),
|
||||
super(
|
||||
tag: 'stream:stream',
|
||||
attributes: <String, String>{
|
||||
'xmlns': componentAcceptXmlns,
|
||||
'xmlns:stream': streamXmlns,
|
||||
'to': jid.domain,
|
||||
},
|
||||
closeTag: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// The states the ComponentToServerNegotiator can be in.
|
||||
enum ComponentToServerState {
|
||||
/// No data has been sent or received yet
|
||||
idle,
|
||||
|
||||
/// Handshake has been sent
|
||||
handshakeSent,
|
||||
}
|
||||
|
||||
/// The ComponentToServerNegotiator is a NegotiationsHandler that allows writing
|
||||
/// components that adhere to XEP-0114.
|
||||
class ComponentToServerNegotiator extends NegotiationsHandler {
|
||||
ComponentToServerNegotiator();
|
||||
|
||||
/// The state the negotiation handler is currently in
|
||||
ComponentToServerState _state = ComponentToServerState.idle;
|
||||
|
||||
@override
|
||||
String getStanzaNamespace() => componentAcceptXmlns;
|
||||
|
||||
@override
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
|
||||
|
||||
@override
|
||||
void sendStreamHeader() {
|
||||
resetStreamParser();
|
||||
sendNonza(
|
||||
XMLNode(
|
||||
tag: 'xml',
|
||||
attributes: {'version': '1.0'},
|
||||
closeTag: false,
|
||||
isDeclaration: true,
|
||||
children: [
|
||||
ComponentStreamHeaderNonza(getConnectionSettings().jid),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _computeHandshake(String id) async {
|
||||
final secret = getConnectionSettings().password;
|
||||
return HEX.encode(
|
||||
(await Sha1().hash(utf8.encode('$streamId$secret'))).bytes,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> negotiate(XMPPStreamObject event) async {
|
||||
switch (_state) {
|
||||
case ComponentToServerState.idle:
|
||||
if (event is XMPPStreamHeader) {
|
||||
streamId = event.attributes['id'];
|
||||
assert(
|
||||
streamId != null,
|
||||
'The server must respond with a stream header that contains an id',
|
||||
);
|
||||
|
||||
_state = ComponentToServerState.handshakeSent;
|
||||
sendNonza(
|
||||
XMLNode(
|
||||
tag: 'handshake',
|
||||
text: await _computeHandshake(streamId!),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
log.severe('Unexpected data received');
|
||||
await handleError(UnexpectedDataError());
|
||||
}
|
||||
break;
|
||||
case ComponentToServerState.handshakeSent:
|
||||
if (event is XMPPStreamElement) {
|
||||
if (event.node.tag == 'handshake' &&
|
||||
event.node.children.isEmpty &&
|
||||
event.node.attributes.isEmpty) {
|
||||
log.info('Successfully authenticated as component');
|
||||
await onNegotiationsDone();
|
||||
} else {
|
||||
log.warning('Handshake failed');
|
||||
await handleError(InvalidHandshakeCredentialsError());
|
||||
}
|
||||
} else {
|
||||
log.severe('Unexpected data received');
|
||||
await handleError(UnexpectedDataError());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_state = ComponentToServerState.idle;
|
||||
|
||||
super.reset();
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,11 @@ Future<void> handleUnhandledStanza(
|
||||
);
|
||||
|
||||
await conn.sendStanza(
|
||||
stanza,
|
||||
awaitable: false,
|
||||
forceEncryption: data.encrypted,
|
||||
StanzaDetails(
|
||||
stanza,
|
||||
awaitable: false,
|
||||
forceEncryption: data.encrypted,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,14 +55,17 @@ class JID {
|
||||
|
||||
/// Converts the JID into a bare JID.
|
||||
JID toBare() {
|
||||
if (isBare()) return this;
|
||||
|
||||
return JID(local, domain, '');
|
||||
}
|
||||
|
||||
/// Converts the JID into one with a resource part of [resource].
|
||||
JID withResource(String resource) => JID(local, domain, resource);
|
||||
|
||||
/// Convert the JID into the JID of the domain. For example, converts alice@example.org/abc123 to example.org.
|
||||
JID toDomain() {
|
||||
return JID('', domain, '');
|
||||
}
|
||||
|
||||
/// Compares the JID with [other]. This function assumes that JID and [other]
|
||||
/// are bare, i.e. only the domain- and localparts are compared. If [ensureBare]
|
||||
/// is optionally set to true, then [other] MUST be bare. Otherwise, false is returned.
|
||||
|
||||
@@ -23,14 +23,7 @@ class XmppManagerAttributes {
|
||||
});
|
||||
|
||||
/// Send a stanza whose response can be awaited.
|
||||
final Future<XMLNode> Function(
|
||||
Stanza stanza, {
|
||||
StanzaFromType addFrom,
|
||||
bool addId,
|
||||
bool awaitable,
|
||||
bool encrypted,
|
||||
bool forceEncryption,
|
||||
}) sendStanza;
|
||||
final Future<XMLNode?> Function(StanzaDetails) sendStanza;
|
||||
|
||||
/// Send a nonza.
|
||||
final void Function(XMLNode) sendNonza;
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/attributes.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
@@ -45,8 +46,7 @@ abstract class XmppManagerBase {
|
||||
);
|
||||
|
||||
final result = await dm!.discoInfoQuery(
|
||||
_managerAttributes.getConnectionSettings().jid.domain,
|
||||
shouldEncrypt: false,
|
||||
_managerAttributes.getConnectionSettings().jid.toDomain(),
|
||||
);
|
||||
if (result.isType<DiscoError>()) {
|
||||
return false;
|
||||
@@ -80,6 +80,9 @@ abstract class XmppManagerBase {
|
||||
/// handler's priority, the earlier it is run.
|
||||
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.
|
||||
List<String> getDiscoFeatures() => [];
|
||||
|
||||
@@ -165,9 +168,11 @@ abstract class XmppManagerBase {
|
||||
);
|
||||
|
||||
await getAttributes().sendStanza(
|
||||
stanza,
|
||||
awaitable: false,
|
||||
forceEncryption: data.encrypted,
|
||||
StanzaDetails(
|
||||
stanza,
|
||||
awaitable: false,
|
||||
forceEncryption: data.encrypted,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +1,59 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0203.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
|
||||
part 'data.freezed.dart';
|
||||
abstract class StanzaHandlerExtension {}
|
||||
|
||||
@freezed
|
||||
class StanzaHandlerData with _$StanzaHandlerData {
|
||||
factory StanzaHandlerData(
|
||||
// Indicates to the runner that processing is now done. This means that all
|
||||
// pre-processing is done and no other handlers should be consulted.
|
||||
bool done,
|
||||
// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||
// should run. The stanza also will not be sent.
|
||||
bool cancel,
|
||||
// The reason why we cancelled the processing and sending
|
||||
dynamic cancelReason,
|
||||
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||
// necessary, e.g. with Message Carbons or OMEMO
|
||||
Stanza stanza, {
|
||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||
// stanza handlers. MUST NOT be overwritten.
|
||||
@Default(false) bool retransmitted,
|
||||
StatelessMediaSharingData? sims,
|
||||
StatelessFileSharingData? sfs,
|
||||
OOBData? oob,
|
||||
StableStanzaId? stableId,
|
||||
ReplyData? reply,
|
||||
ChatState? chatState,
|
||||
@Default(false) bool isCarbon,
|
||||
@Default(false) bool deliveryReceiptRequested,
|
||||
@Default(false) bool isMarkable,
|
||||
// File Upload Notifications
|
||||
// A notification
|
||||
FileMetadataData? fun,
|
||||
// The stanza id this replaces
|
||||
String? funReplacement,
|
||||
// The stanza id this cancels
|
||||
String? funCancellation,
|
||||
// Whether the stanza was received encrypted
|
||||
@Default(false) bool encrypted,
|
||||
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||
// to the JID anyway.
|
||||
@Default(false) bool forceEncryption,
|
||||
// The stated type of encryption used, if any was used
|
||||
ExplicitEncryptionType? encryptionType,
|
||||
// Delayed Delivery
|
||||
DelayedDelivery? delayedDelivery,
|
||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
||||
// pass data around.
|
||||
@Default(<String, dynamic>{}) Map<String, dynamic> other,
|
||||
// If non-null, then it indicates the origin Id of the message that should be
|
||||
// retracted
|
||||
MessageRetractionData? messageRetraction,
|
||||
// If non-null, then the message is a correction for the specified stanza Id
|
||||
String? lastMessageCorrectionSid,
|
||||
// Reactions data
|
||||
MessageReactions? messageReactions,
|
||||
// The Id of the sticker pack this sticker belongs to
|
||||
String? stickerPackId,
|
||||
}) = _StanzaHandlerData;
|
||||
class StanzaHandlerData {
|
||||
StanzaHandlerData(
|
||||
this.done,
|
||||
this.cancel,
|
||||
this.stanza,
|
||||
this.extensions, {
|
||||
this.cancelReason,
|
||||
this.encryptionError,
|
||||
this.encrypted = false,
|
||||
this.forceEncryption = false,
|
||||
this.shouldEncrypt = true,
|
||||
this.skip = false,
|
||||
});
|
||||
|
||||
/// Indicates to the runner that processing is now done. This means that all
|
||||
/// pre-processing is done and no other handlers should be consulted.
|
||||
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
|
||||
/// should run. The stanza also will not be sent.
|
||||
bool cancel;
|
||||
|
||||
/// The reason why we cancelled the processing and sending.
|
||||
Object? cancelReason;
|
||||
|
||||
/// The reason why an encryption or decryption failed.
|
||||
Object? encryptionError;
|
||||
|
||||
/// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is
|
||||
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
||||
Stanza stanza;
|
||||
|
||||
/// Whether the stanza is already encrypted
|
||||
bool encrypted;
|
||||
|
||||
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||
// to the JID anyway.
|
||||
bool forceEncryption;
|
||||
|
||||
/// Flag indicating whether a E2EE implementation should encrypt the stanza (true)
|
||||
/// or not (false).
|
||||
bool shouldEncrypt;
|
||||
|
||||
/// Additional data from other managers.
|
||||
final TypedMap<StanzaHandlerExtension> extensions;
|
||||
}
|
||||
|
||||
@@ -1,747 +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
|
||||
|
||||
part of 'data.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');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$StanzaHandlerData {
|
||||
// Indicates to the runner that processing is now done. This means that all
|
||||
// pre-processing is done and no other handlers should be consulted.
|
||||
bool get done =>
|
||||
throw _privateConstructorUsedError; // Indicates to the runner that processing is to be cancelled and no further handlers
|
||||
// should run. The stanza also will not be sent.
|
||||
bool get cancel =>
|
||||
throw _privateConstructorUsedError; // The reason why we cancelled the processing and sending
|
||||
dynamic get cancelReason =>
|
||||
throw _privateConstructorUsedError; // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||
// necessary, e.g. with Message Carbons or OMEMO
|
||||
Stanza get stanza =>
|
||||
throw _privateConstructorUsedError; // Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||
// stanza handlers. MUST NOT be overwritten.
|
||||
bool get retransmitted => throw _privateConstructorUsedError;
|
||||
StatelessMediaSharingData? get sims => throw _privateConstructorUsedError;
|
||||
StatelessFileSharingData? get sfs => throw _privateConstructorUsedError;
|
||||
OOBData? get oob => throw _privateConstructorUsedError;
|
||||
StableStanzaId? get stableId => throw _privateConstructorUsedError;
|
||||
ReplyData? get reply => throw _privateConstructorUsedError;
|
||||
ChatState? get chatState => throw _privateConstructorUsedError;
|
||||
bool get isCarbon => throw _privateConstructorUsedError;
|
||||
bool get deliveryReceiptRequested => throw _privateConstructorUsedError;
|
||||
bool get isMarkable =>
|
||||
throw _privateConstructorUsedError; // File Upload Notifications
|
||||
// A notification
|
||||
FileMetadataData? get fun =>
|
||||
throw _privateConstructorUsedError; // The stanza id this replaces
|
||||
String? get funReplacement =>
|
||||
throw _privateConstructorUsedError; // The stanza id this cancels
|
||||
String? get funCancellation =>
|
||||
throw _privateConstructorUsedError; // Whether the stanza was received encrypted
|
||||
bool get encrypted =>
|
||||
throw _privateConstructorUsedError; // If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||
// to the JID anyway.
|
||||
bool get forceEncryption =>
|
||||
throw _privateConstructorUsedError; // The stated type of encryption used, if any was used
|
||||
ExplicitEncryptionType? get encryptionType =>
|
||||
throw _privateConstructorUsedError; // Delayed Delivery
|
||||
DelayedDelivery? get delayedDelivery =>
|
||||
throw _privateConstructorUsedError; // This is for stanza handlers that are not part of the XMPP library but still need
|
||||
// pass data around.
|
||||
Map<String, dynamic> get other =>
|
||||
throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be
|
||||
// retracted
|
||||
MessageRetractionData? get messageRetraction =>
|
||||
throw _privateConstructorUsedError; // If non-null, then the message is a correction for the specified stanza Id
|
||||
String? get lastMessageCorrectionSid =>
|
||||
throw _privateConstructorUsedError; // Reactions data
|
||||
MessageReactions? get messageReactions =>
|
||||
throw _privateConstructorUsedError; // The Id of the sticker pack this sticker belongs to
|
||||
String? get stickerPackId => throw _privateConstructorUsedError;
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $StanzaHandlerDataCopyWith<$Res> {
|
||||
factory $StanzaHandlerDataCopyWith(
|
||||
StanzaHandlerData value, $Res Function(StanzaHandlerData) then) =
|
||||
_$StanzaHandlerDataCopyWithImpl<$Res>;
|
||||
$Res call(
|
||||
{bool done,
|
||||
bool cancel,
|
||||
dynamic cancelReason,
|
||||
Stanza stanza,
|
||||
bool retransmitted,
|
||||
StatelessMediaSharingData? sims,
|
||||
StatelessFileSharingData? sfs,
|
||||
OOBData? oob,
|
||||
StableStanzaId? stableId,
|
||||
ReplyData? reply,
|
||||
ChatState? chatState,
|
||||
bool isCarbon,
|
||||
bool deliveryReceiptRequested,
|
||||
bool isMarkable,
|
||||
FileMetadataData? fun,
|
||||
String? funReplacement,
|
||||
String? funCancellation,
|
||||
bool encrypted,
|
||||
bool forceEncryption,
|
||||
ExplicitEncryptionType? encryptionType,
|
||||
DelayedDelivery? delayedDelivery,
|
||||
Map<String, dynamic> other,
|
||||
MessageRetractionData? messageRetraction,
|
||||
String? lastMessageCorrectionSid,
|
||||
MessageReactions? messageReactions,
|
||||
String? stickerPackId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$StanzaHandlerDataCopyWithImpl<$Res>
|
||||
implements $StanzaHandlerDataCopyWith<$Res> {
|
||||
_$StanzaHandlerDataCopyWithImpl(this._value, this._then);
|
||||
|
||||
final StanzaHandlerData _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(StanzaHandlerData) _then;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? done = freezed,
|
||||
Object? cancel = freezed,
|
||||
Object? cancelReason = freezed,
|
||||
Object? stanza = freezed,
|
||||
Object? retransmitted = freezed,
|
||||
Object? sims = freezed,
|
||||
Object? sfs = freezed,
|
||||
Object? oob = freezed,
|
||||
Object? stableId = freezed,
|
||||
Object? reply = freezed,
|
||||
Object? chatState = freezed,
|
||||
Object? isCarbon = freezed,
|
||||
Object? deliveryReceiptRequested = freezed,
|
||||
Object? isMarkable = freezed,
|
||||
Object? fun = freezed,
|
||||
Object? funReplacement = freezed,
|
||||
Object? funCancellation = freezed,
|
||||
Object? encrypted = freezed,
|
||||
Object? forceEncryption = freezed,
|
||||
Object? encryptionType = freezed,
|
||||
Object? delayedDelivery = freezed,
|
||||
Object? other = freezed,
|
||||
Object? messageRetraction = freezed,
|
||||
Object? lastMessageCorrectionSid = freezed,
|
||||
Object? messageReactions = freezed,
|
||||
Object? stickerPackId = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
done: done == freezed
|
||||
? _value.done
|
||||
: done // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
cancel: cancel == freezed
|
||||
? _value.cancel
|
||||
: cancel // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
cancelReason: cancelReason == freezed
|
||||
? _value.cancelReason
|
||||
: cancelReason // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
stanza: stanza == freezed
|
||||
? _value.stanza
|
||||
: stanza // ignore: cast_nullable_to_non_nullable
|
||||
as Stanza,
|
||||
retransmitted: retransmitted == freezed
|
||||
? _value.retransmitted
|
||||
: retransmitted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
sims: sims == freezed
|
||||
? _value.sims
|
||||
: sims // ignore: cast_nullable_to_non_nullable
|
||||
as StatelessMediaSharingData?,
|
||||
sfs: sfs == freezed
|
||||
? _value.sfs
|
||||
: sfs // ignore: cast_nullable_to_non_nullable
|
||||
as StatelessFileSharingData?,
|
||||
oob: oob == freezed
|
||||
? _value.oob
|
||||
: oob // ignore: cast_nullable_to_non_nullable
|
||||
as OOBData?,
|
||||
stableId: stableId == freezed
|
||||
? _value.stableId
|
||||
: stableId // ignore: cast_nullable_to_non_nullable
|
||||
as StableStanzaId?,
|
||||
reply: reply == freezed
|
||||
? _value.reply
|
||||
: reply // ignore: cast_nullable_to_non_nullable
|
||||
as ReplyData?,
|
||||
chatState: chatState == freezed
|
||||
? _value.chatState
|
||||
: chatState // ignore: cast_nullable_to_non_nullable
|
||||
as ChatState?,
|
||||
isCarbon: isCarbon == freezed
|
||||
? _value.isCarbon
|
||||
: isCarbon // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
deliveryReceiptRequested: deliveryReceiptRequested == freezed
|
||||
? _value.deliveryReceiptRequested
|
||||
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isMarkable: isMarkable == freezed
|
||||
? _value.isMarkable
|
||||
: isMarkable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
fun: fun == freezed
|
||||
? _value.fun
|
||||
: fun // ignore: cast_nullable_to_non_nullable
|
||||
as FileMetadataData?,
|
||||
funReplacement: funReplacement == freezed
|
||||
? _value.funReplacement
|
||||
: funReplacement // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
funCancellation: funCancellation == freezed
|
||||
? _value.funCancellation
|
||||
: funCancellation // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
encrypted: encrypted == freezed
|
||||
? _value.encrypted
|
||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
forceEncryption: forceEncryption == freezed
|
||||
? _value.forceEncryption
|
||||
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
encryptionType: encryptionType == freezed
|
||||
? _value.encryptionType
|
||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
||||
as ExplicitEncryptionType?,
|
||||
delayedDelivery: delayedDelivery == freezed
|
||||
? _value.delayedDelivery
|
||||
: delayedDelivery // ignore: cast_nullable_to_non_nullable
|
||||
as DelayedDelivery?,
|
||||
other: other == freezed
|
||||
? _value.other
|
||||
: other // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
messageRetraction: messageRetraction == freezed
|
||||
? _value.messageRetraction
|
||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
||||
as MessageRetractionData?,
|
||||
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
|
||||
? _value.lastMessageCorrectionSid
|
||||
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
messageReactions: messageReactions == freezed
|
||||
? _value.messageReactions
|
||||
: messageReactions // ignore: cast_nullable_to_non_nullable
|
||||
as MessageReactions?,
|
||||
stickerPackId: stickerPackId == freezed
|
||||
? _value.stickerPackId
|
||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$_StanzaHandlerDataCopyWith<$Res>
|
||||
implements $StanzaHandlerDataCopyWith<$Res> {
|
||||
factory _$$_StanzaHandlerDataCopyWith(_$_StanzaHandlerData value,
|
||||
$Res Function(_$_StanzaHandlerData) then) =
|
||||
__$$_StanzaHandlerDataCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call(
|
||||
{bool done,
|
||||
bool cancel,
|
||||
dynamic cancelReason,
|
||||
Stanza stanza,
|
||||
bool retransmitted,
|
||||
StatelessMediaSharingData? sims,
|
||||
StatelessFileSharingData? sfs,
|
||||
OOBData? oob,
|
||||
StableStanzaId? stableId,
|
||||
ReplyData? reply,
|
||||
ChatState? chatState,
|
||||
bool isCarbon,
|
||||
bool deliveryReceiptRequested,
|
||||
bool isMarkable,
|
||||
FileMetadataData? fun,
|
||||
String? funReplacement,
|
||||
String? funCancellation,
|
||||
bool encrypted,
|
||||
bool forceEncryption,
|
||||
ExplicitEncryptionType? encryptionType,
|
||||
DelayedDelivery? delayedDelivery,
|
||||
Map<String, dynamic> other,
|
||||
MessageRetractionData? messageRetraction,
|
||||
String? lastMessageCorrectionSid,
|
||||
MessageReactions? messageReactions,
|
||||
String? stickerPackId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
||||
extends _$StanzaHandlerDataCopyWithImpl<$Res>
|
||||
implements _$$_StanzaHandlerDataCopyWith<$Res> {
|
||||
__$$_StanzaHandlerDataCopyWithImpl(
|
||||
_$_StanzaHandlerData _value, $Res Function(_$_StanzaHandlerData) _then)
|
||||
: super(_value, (v) => _then(v as _$_StanzaHandlerData));
|
||||
|
||||
@override
|
||||
_$_StanzaHandlerData get _value => super._value as _$_StanzaHandlerData;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? done = freezed,
|
||||
Object? cancel = freezed,
|
||||
Object? cancelReason = freezed,
|
||||
Object? stanza = freezed,
|
||||
Object? retransmitted = freezed,
|
||||
Object? sims = freezed,
|
||||
Object? sfs = freezed,
|
||||
Object? oob = freezed,
|
||||
Object? stableId = freezed,
|
||||
Object? reply = freezed,
|
||||
Object? chatState = freezed,
|
||||
Object? isCarbon = freezed,
|
||||
Object? deliveryReceiptRequested = freezed,
|
||||
Object? isMarkable = freezed,
|
||||
Object? fun = freezed,
|
||||
Object? funReplacement = freezed,
|
||||
Object? funCancellation = freezed,
|
||||
Object? encrypted = freezed,
|
||||
Object? forceEncryption = freezed,
|
||||
Object? encryptionType = freezed,
|
||||
Object? delayedDelivery = freezed,
|
||||
Object? other = freezed,
|
||||
Object? messageRetraction = freezed,
|
||||
Object? lastMessageCorrectionSid = freezed,
|
||||
Object? messageReactions = freezed,
|
||||
Object? stickerPackId = freezed,
|
||||
}) {
|
||||
return _then(_$_StanzaHandlerData(
|
||||
done == freezed
|
||||
? _value.done
|
||||
: done // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
cancel == freezed
|
||||
? _value.cancel
|
||||
: cancel // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
cancelReason == freezed
|
||||
? _value.cancelReason
|
||||
: cancelReason // ignore: cast_nullable_to_non_nullable
|
||||
as dynamic,
|
||||
stanza == freezed
|
||||
? _value.stanza
|
||||
: stanza // ignore: cast_nullable_to_non_nullable
|
||||
as Stanza,
|
||||
retransmitted: retransmitted == freezed
|
||||
? _value.retransmitted
|
||||
: retransmitted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
sims: sims == freezed
|
||||
? _value.sims
|
||||
: sims // ignore: cast_nullable_to_non_nullable
|
||||
as StatelessMediaSharingData?,
|
||||
sfs: sfs == freezed
|
||||
? _value.sfs
|
||||
: sfs // ignore: cast_nullable_to_non_nullable
|
||||
as StatelessFileSharingData?,
|
||||
oob: oob == freezed
|
||||
? _value.oob
|
||||
: oob // ignore: cast_nullable_to_non_nullable
|
||||
as OOBData?,
|
||||
stableId: stableId == freezed
|
||||
? _value.stableId
|
||||
: stableId // ignore: cast_nullable_to_non_nullable
|
||||
as StableStanzaId?,
|
||||
reply: reply == freezed
|
||||
? _value.reply
|
||||
: reply // ignore: cast_nullable_to_non_nullable
|
||||
as ReplyData?,
|
||||
chatState: chatState == freezed
|
||||
? _value.chatState
|
||||
: chatState // ignore: cast_nullable_to_non_nullable
|
||||
as ChatState?,
|
||||
isCarbon: isCarbon == freezed
|
||||
? _value.isCarbon
|
||||
: isCarbon // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
deliveryReceiptRequested: deliveryReceiptRequested == freezed
|
||||
? _value.deliveryReceiptRequested
|
||||
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
isMarkable: isMarkable == freezed
|
||||
? _value.isMarkable
|
||||
: isMarkable // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
fun: fun == freezed
|
||||
? _value.fun
|
||||
: fun // ignore: cast_nullable_to_non_nullable
|
||||
as FileMetadataData?,
|
||||
funReplacement: funReplacement == freezed
|
||||
? _value.funReplacement
|
||||
: funReplacement // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
funCancellation: funCancellation == freezed
|
||||
? _value.funCancellation
|
||||
: funCancellation // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
encrypted: encrypted == freezed
|
||||
? _value.encrypted
|
||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
forceEncryption: forceEncryption == freezed
|
||||
? _value.forceEncryption
|
||||
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
encryptionType: encryptionType == freezed
|
||||
? _value.encryptionType
|
||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
||||
as ExplicitEncryptionType?,
|
||||
delayedDelivery: delayedDelivery == freezed
|
||||
? _value.delayedDelivery
|
||||
: delayedDelivery // ignore: cast_nullable_to_non_nullable
|
||||
as DelayedDelivery?,
|
||||
other: other == freezed
|
||||
? _value._other
|
||||
: other // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>,
|
||||
messageRetraction: messageRetraction == freezed
|
||||
? _value.messageRetraction
|
||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
||||
as MessageRetractionData?,
|
||||
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
|
||||
? _value.lastMessageCorrectionSid
|
||||
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
messageReactions: messageReactions == freezed
|
||||
? _value.messageReactions
|
||||
: messageReactions // ignore: cast_nullable_to_non_nullable
|
||||
as MessageReactions?,
|
||||
stickerPackId: stickerPackId == freezed
|
||||
? _value.stickerPackId
|
||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$_StanzaHandlerData implements _StanzaHandlerData {
|
||||
_$_StanzaHandlerData(this.done, this.cancel, this.cancelReason, this.stanza,
|
||||
{this.retransmitted = false,
|
||||
this.sims,
|
||||
this.sfs,
|
||||
this.oob,
|
||||
this.stableId,
|
||||
this.reply,
|
||||
this.chatState,
|
||||
this.isCarbon = false,
|
||||
this.deliveryReceiptRequested = false,
|
||||
this.isMarkable = false,
|
||||
this.fun,
|
||||
this.funReplacement,
|
||||
this.funCancellation,
|
||||
this.encrypted = false,
|
||||
this.forceEncryption = false,
|
||||
this.encryptionType,
|
||||
this.delayedDelivery,
|
||||
final Map<String, dynamic> other = const <String, dynamic>{},
|
||||
this.messageRetraction,
|
||||
this.lastMessageCorrectionSid,
|
||||
this.messageReactions,
|
||||
this.stickerPackId})
|
||||
: _other = other;
|
||||
|
||||
// Indicates to the runner that processing is now done. This means that all
|
||||
// pre-processing is done and no other handlers should be consulted.
|
||||
@override
|
||||
final bool done;
|
||||
// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||
// should run. The stanza also will not be sent.
|
||||
@override
|
||||
final bool cancel;
|
||||
// The reason why we cancelled the processing and sending
|
||||
@override
|
||||
final dynamic cancelReason;
|
||||
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||
// necessary, e.g. with Message Carbons or OMEMO
|
||||
@override
|
||||
final Stanza stanza;
|
||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||
// stanza handlers. MUST NOT be overwritten.
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool retransmitted;
|
||||
@override
|
||||
final StatelessMediaSharingData? sims;
|
||||
@override
|
||||
final StatelessFileSharingData? sfs;
|
||||
@override
|
||||
final OOBData? oob;
|
||||
@override
|
||||
final StableStanzaId? stableId;
|
||||
@override
|
||||
final ReplyData? reply;
|
||||
@override
|
||||
final ChatState? chatState;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isCarbon;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool deliveryReceiptRequested;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isMarkable;
|
||||
// File Upload Notifications
|
||||
// A notification
|
||||
@override
|
||||
final FileMetadataData? fun;
|
||||
// The stanza id this replaces
|
||||
@override
|
||||
final String? funReplacement;
|
||||
// The stanza id this cancels
|
||||
@override
|
||||
final String? funCancellation;
|
||||
// Whether the stanza was received encrypted
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool encrypted;
|
||||
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||
// to the JID anyway.
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool forceEncryption;
|
||||
// The stated type of encryption used, if any was used
|
||||
@override
|
||||
final ExplicitEncryptionType? encryptionType;
|
||||
// Delayed Delivery
|
||||
@override
|
||||
final DelayedDelivery? delayedDelivery;
|
||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
||||
// pass data around.
|
||||
final Map<String, dynamic> _other;
|
||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
||||
// pass data around.
|
||||
@override
|
||||
@JsonKey()
|
||||
Map<String, dynamic> get other {
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_other);
|
||||
}
|
||||
|
||||
// If non-null, then it indicates the origin Id of the message that should be
|
||||
// retracted
|
||||
@override
|
||||
final MessageRetractionData? messageRetraction;
|
||||
// If non-null, then the message is a correction for the specified stanza Id
|
||||
@override
|
||||
final String? lastMessageCorrectionSid;
|
||||
// Reactions data
|
||||
@override
|
||||
final MessageReactions? messageReactions;
|
||||
// The Id of the sticker pack this sticker belongs to
|
||||
@override
|
||||
final String? stickerPackId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, stableId: $stableId, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, forceEncryption: $forceEncryption, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction, lastMessageCorrectionSid: $lastMessageCorrectionSid, messageReactions: $messageReactions, stickerPackId: $stickerPackId)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$_StanzaHandlerData &&
|
||||
const DeepCollectionEquality().equals(other.done, done) &&
|
||||
const DeepCollectionEquality().equals(other.cancel, cancel) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.cancelReason, cancelReason) &&
|
||||
const DeepCollectionEquality().equals(other.stanza, stanza) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.retransmitted, retransmitted) &&
|
||||
const DeepCollectionEquality().equals(other.sims, sims) &&
|
||||
const DeepCollectionEquality().equals(other.sfs, sfs) &&
|
||||
const DeepCollectionEquality().equals(other.oob, oob) &&
|
||||
const DeepCollectionEquality().equals(other.stableId, stableId) &&
|
||||
const DeepCollectionEquality().equals(other.reply, reply) &&
|
||||
const DeepCollectionEquality().equals(other.chatState, chatState) &&
|
||||
const DeepCollectionEquality().equals(other.isCarbon, isCarbon) &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other.deliveryReceiptRequested, deliveryReceiptRequested) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.isMarkable, isMarkable) &&
|
||||
const DeepCollectionEquality().equals(other.fun, fun) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.funReplacement, funReplacement) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.funCancellation, funCancellation) &&
|
||||
const DeepCollectionEquality().equals(other.encrypted, encrypted) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.forceEncryption, forceEncryption) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.encryptionType, encryptionType) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.delayedDelivery, delayedDelivery) &&
|
||||
const DeepCollectionEquality().equals(other._other, this._other) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.messageRetraction, messageRetraction) &&
|
||||
const DeepCollectionEquality().equals(
|
||||
other.lastMessageCorrectionSid, lastMessageCorrectionSid) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.messageReactions, messageReactions) &&
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.stickerPackId, stickerPackId));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hashAll([
|
||||
runtimeType,
|
||||
const DeepCollectionEquality().hash(done),
|
||||
const DeepCollectionEquality().hash(cancel),
|
||||
const DeepCollectionEquality().hash(cancelReason),
|
||||
const DeepCollectionEquality().hash(stanza),
|
||||
const DeepCollectionEquality().hash(retransmitted),
|
||||
const DeepCollectionEquality().hash(sims),
|
||||
const DeepCollectionEquality().hash(sfs),
|
||||
const DeepCollectionEquality().hash(oob),
|
||||
const DeepCollectionEquality().hash(stableId),
|
||||
const DeepCollectionEquality().hash(reply),
|
||||
const DeepCollectionEquality().hash(chatState),
|
||||
const DeepCollectionEquality().hash(isCarbon),
|
||||
const DeepCollectionEquality().hash(deliveryReceiptRequested),
|
||||
const DeepCollectionEquality().hash(isMarkable),
|
||||
const DeepCollectionEquality().hash(fun),
|
||||
const DeepCollectionEquality().hash(funReplacement),
|
||||
const DeepCollectionEquality().hash(funCancellation),
|
||||
const DeepCollectionEquality().hash(encrypted),
|
||||
const DeepCollectionEquality().hash(forceEncryption),
|
||||
const DeepCollectionEquality().hash(encryptionType),
|
||||
const DeepCollectionEquality().hash(delayedDelivery),
|
||||
const DeepCollectionEquality().hash(_other),
|
||||
const DeepCollectionEquality().hash(messageRetraction),
|
||||
const DeepCollectionEquality().hash(lastMessageCorrectionSid),
|
||||
const DeepCollectionEquality().hash(messageReactions),
|
||||
const DeepCollectionEquality().hash(stickerPackId)
|
||||
]);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
||||
__$$_StanzaHandlerDataCopyWithImpl<_$_StanzaHandlerData>(
|
||||
this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _StanzaHandlerData implements StanzaHandlerData {
|
||||
factory _StanzaHandlerData(final bool done, final bool cancel,
|
||||
final dynamic cancelReason, final Stanza stanza,
|
||||
{final bool retransmitted,
|
||||
final StatelessMediaSharingData? sims,
|
||||
final StatelessFileSharingData? sfs,
|
||||
final OOBData? oob,
|
||||
final StableStanzaId? stableId,
|
||||
final ReplyData? reply,
|
||||
final ChatState? chatState,
|
||||
final bool isCarbon,
|
||||
final bool deliveryReceiptRequested,
|
||||
final bool isMarkable,
|
||||
final FileMetadataData? fun,
|
||||
final String? funReplacement,
|
||||
final String? funCancellation,
|
||||
final bool encrypted,
|
||||
final bool forceEncryption,
|
||||
final ExplicitEncryptionType? encryptionType,
|
||||
final DelayedDelivery? delayedDelivery,
|
||||
final Map<String, dynamic> other,
|
||||
final MessageRetractionData? messageRetraction,
|
||||
final String? lastMessageCorrectionSid,
|
||||
final MessageReactions? messageReactions,
|
||||
final String? stickerPackId}) = _$_StanzaHandlerData;
|
||||
|
||||
@override // Indicates to the runner that processing is now done. This means that all
|
||||
// pre-processing is done and no other handlers should be consulted.
|
||||
bool get done;
|
||||
@override // Indicates to the runner that processing is to be cancelled and no further handlers
|
||||
// should run. The stanza also will not be sent.
|
||||
bool get cancel;
|
||||
@override // The reason why we cancelled the processing and sending
|
||||
dynamic get cancelReason;
|
||||
@override // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||
// necessary, e.g. with Message Carbons or OMEMO
|
||||
Stanza get stanza;
|
||||
@override // Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||
// stanza handlers. MUST NOT be overwritten.
|
||||
bool get retransmitted;
|
||||
@override
|
||||
StatelessMediaSharingData? get sims;
|
||||
@override
|
||||
StatelessFileSharingData? get sfs;
|
||||
@override
|
||||
OOBData? get oob;
|
||||
@override
|
||||
StableStanzaId? get stableId;
|
||||
@override
|
||||
ReplyData? get reply;
|
||||
@override
|
||||
ChatState? get chatState;
|
||||
@override
|
||||
bool get isCarbon;
|
||||
@override
|
||||
bool get deliveryReceiptRequested;
|
||||
@override
|
||||
bool get isMarkable;
|
||||
@override // File Upload Notifications
|
||||
// A notification
|
||||
FileMetadataData? get fun;
|
||||
@override // The stanza id this replaces
|
||||
String? get funReplacement;
|
||||
@override // The stanza id this cancels
|
||||
String? get funCancellation;
|
||||
@override // Whether the stanza was received encrypted
|
||||
bool get encrypted;
|
||||
@override // If true, forces the encryption manager to encrypt to the JID, even if it
|
||||
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||
// to the JID anyway.
|
||||
bool get forceEncryption;
|
||||
@override // The stated type of encryption used, if any was used
|
||||
ExplicitEncryptionType? get encryptionType;
|
||||
@override // Delayed Delivery
|
||||
DelayedDelivery? get delayedDelivery;
|
||||
@override // This is for stanza handlers that are not part of the XMPP library but still need
|
||||
// pass data around.
|
||||
Map<String, dynamic> get other;
|
||||
@override // If non-null, then it indicates the origin Id of the message that should be
|
||||
// retracted
|
||||
MessageRetractionData? get messageRetraction;
|
||||
@override // If non-null, then the message is a correction for the specified stanza Id
|
||||
String? get lastMessageCorrectionSid;
|
||||
@override // Reactions data
|
||||
MessageReactions? get messageReactions;
|
||||
@override // The Id of the sticker pack this sticker belongs to
|
||||
String? get stickerPackId;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -1,89 +1,109 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// A Handler is responsible for matching any kind of toplevel item in the XML stream
|
||||
/// (stanzas and Nonzas). For that, its [matches] method is called. What happens
|
||||
/// next depends on the subclass.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class Handler {
|
||||
const Handler(this.matchStanzas, {this.nonzaTag, this.nonzaXmlns});
|
||||
final String? nonzaTag;
|
||||
final String? nonzaXmlns;
|
||||
final bool matchStanzas;
|
||||
|
||||
/// Returns true if the node matches the description provided by this [Handler].
|
||||
bool matches(XMLNode node);
|
||||
}
|
||||
|
||||
/// A Handler that specialises in matching Nonzas (and stanzas).
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
this.nonzaTag,
|
||||
this.nonzaXmlns,
|
||||
});
|
||||
|
||||
/// The function to call when a nonza matches the description.
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
|
||||
/// The expected tag of a matching nonza.
|
||||
final String? nonzaTag;
|
||||
|
||||
// The expected xmlns attribute of a matching nonza.
|
||||
final String? nonzaXmlns;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = false;
|
||||
|
||||
var matches = true;
|
||||
if (nonzaTag == null && nonzaXmlns == null) {
|
||||
matches = true;
|
||||
}
|
||||
|
||||
if (nonzaXmlns != null && nonzaTag != null) {
|
||||
matches = (node.attributes['xmlns'] ?? '') == nonzaXmlns! &&
|
||||
node.tag == nonzaTag!;
|
||||
}
|
||||
|
||||
if (matchStanzas && nonzaTag == null) {
|
||||
matches = ['iq', 'presence', 'message'].contains(node.tag);
|
||||
return true;
|
||||
} else {
|
||||
if (nonzaXmlns != null) {
|
||||
matches &= node.attributes['xmlns'] == nonzaXmlns;
|
||||
}
|
||||
if (nonzaTag != null) {
|
||||
matches &= node.tag == nonzaTag;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
String? nonzaTag,
|
||||
String? nonzaXmlns,
|
||||
}) : super(
|
||||
false,
|
||||
nonzaTag: nonzaTag,
|
||||
nonzaXmlns: nonzaXmlns,
|
||||
);
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
}
|
||||
|
||||
/// A Handler that only matches stanzas.
|
||||
class StanzaHandler extends Handler {
|
||||
StanzaHandler({
|
||||
required this.callback,
|
||||
this.tagXmlns,
|
||||
this.tagName,
|
||||
this.priority = 0,
|
||||
String? stanzaTag,
|
||||
}) : super(
|
||||
true,
|
||||
nonzaTag: stanzaTag,
|
||||
nonzaXmlns: stanzaXmlns,
|
||||
);
|
||||
this.stanzaTag,
|
||||
this.xmlns,
|
||||
});
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a tag equal to
|
||||
/// [tagName].
|
||||
final String? tagName;
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a xmlns attribute
|
||||
/// equal to [tagXmlns]. If [tagName] is also non-null, then the element must also
|
||||
/// have a tag equal to [tagName].
|
||||
final String? tagXmlns;
|
||||
|
||||
/// If specified, the matching stanza must have a tag equal to [stanzaTag].
|
||||
final String? stanzaTag;
|
||||
|
||||
/// If specified, then the stanza must have a xmlns attribute equal to [xmlns].
|
||||
/// This defaults to [stanzaXmlns], but can be set to any other value or null. This
|
||||
/// is useful, for example, for components.
|
||||
final String? xmlns;
|
||||
|
||||
/// The priority after which [StanzaHandler]s are sorted.
|
||||
final int priority;
|
||||
|
||||
/// The function to call when a stanza matches the description.
|
||||
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = super.matches(node);
|
||||
|
||||
if (matches == false) {
|
||||
return false;
|
||||
var matches = ['iq', 'message', 'presence'].contains(node.tag);
|
||||
if (stanzaTag != null) {
|
||||
matches &= node.tag == stanzaTag;
|
||||
}
|
||||
if (xmlns != null) {
|
||||
matches &= node.xmlns == xmlns;
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
|
||||
matches &= firstTag != null;
|
||||
|
||||
matches = firstTag != null;
|
||||
if (tagXmlns != null) {
|
||||
matches &= firstTag?.xmlns == tagXmlns;
|
||||
}
|
||||
} else if (tagXmlns != null) {
|
||||
return listContains(
|
||||
node.children,
|
||||
(XMLNode node_) =>
|
||||
node_.attributes.containsKey('xmlns') &&
|
||||
node_.attributes['xmlns'] == tagXmlns,
|
||||
);
|
||||
}
|
||||
|
||||
if (tagName == null && tagXmlns == null) {
|
||||
matches = true;
|
||||
matches &= node.children.firstWhereOrNull(
|
||||
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||
) !=
|
||||
null;
|
||||
}
|
||||
|
||||
return matches;
|
||||
|
||||
@@ -31,3 +31,6 @@ const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
|
||||
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
||||
const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
|
||||
const occupantIdManager = 'org.moxxmpp.occupantidmanager';
|
||||
const mucManager = 'org.moxxmpp.mucmanager';
|
||||
|
||||
@@ -1,325 +1,153 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:collection/collection.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/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0184.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0308.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0449.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||
|
||||
/// Data used to build a message stanza.
|
||||
///
|
||||
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
|
||||
/// added. This is recommended when sharing files but may cause issues when the message
|
||||
/// stanza should include a SFS element without any fallbacks.
|
||||
class MessageDetails {
|
||||
const MessageDetails({
|
||||
required this.to,
|
||||
this.body,
|
||||
this.requestDeliveryReceipt = false,
|
||||
this.requestChatMarkers = true,
|
||||
this.id,
|
||||
this.originId,
|
||||
this.quoteBody,
|
||||
this.quoteId,
|
||||
this.quoteFrom,
|
||||
this.chatState,
|
||||
this.sfs,
|
||||
this.fun,
|
||||
this.funReplacement,
|
||||
this.funCancellation,
|
||||
this.shouldEncrypt = false,
|
||||
this.messageRetraction,
|
||||
this.lastMessageCorrectionId,
|
||||
this.messageReactions,
|
||||
this.messageProcessingHints,
|
||||
this.stickerPackId,
|
||||
this.setOOBFallbackBody = true,
|
||||
});
|
||||
final String to;
|
||||
/// A callback that is called whenever a message is sent using
|
||||
/// [MessageManager.sendMessage]. The input the typed map that is passed to
|
||||
/// sendMessage.
|
||||
typedef MessageSendingCallback = List<XMLNode> Function(
|
||||
TypedMap<StanzaHandlerExtension>,
|
||||
);
|
||||
|
||||
/// The raw content of the <body /> element.
|
||||
class MessageBodyData implements StanzaHandlerExtension {
|
||||
const MessageBodyData(this.body);
|
||||
|
||||
/// The content of the <body /> element.
|
||||
final String? body;
|
||||
final bool requestDeliveryReceipt;
|
||||
final bool requestChatMarkers;
|
||||
final String? id;
|
||||
final String? originId;
|
||||
final String? quoteBody;
|
||||
final String? quoteId;
|
||||
final String? quoteFrom;
|
||||
final ChatState? chatState;
|
||||
final StatelessFileSharingData? sfs;
|
||||
final FileMetadataData? fun;
|
||||
final String? funReplacement;
|
||||
final String? funCancellation;
|
||||
final bool shouldEncrypt;
|
||||
final MessageRetractionData? messageRetraction;
|
||||
final String? lastMessageCorrectionId;
|
||||
final MessageReactions? messageReactions;
|
||||
final String? stickerPackId;
|
||||
final List<MessageProcessingHint>? messageProcessingHints;
|
||||
final bool setOOBFallbackBody;
|
||||
|
||||
XMLNode toXML() {
|
||||
return XMLNode(
|
||||
tag: 'body',
|
||||
text: body,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The id attribute of the message stanza.
|
||||
class MessageIdData implements StanzaHandlerExtension {
|
||||
const MessageIdData(this.id);
|
||||
|
||||
/// The id attribute of the stanza.
|
||||
final String id;
|
||||
}
|
||||
|
||||
class MessageManager extends XmppManagerBase {
|
||||
MessageManager() : super(messageManager);
|
||||
|
||||
/// The priority of the message handler. If a handler should run before this one,
|
||||
/// which emits the [MessageEvent] event and terminates processing, make sure it
|
||||
/// has a priority greater than [messageHandlerPriority].
|
||||
static int messageHandlerPriority = -100;
|
||||
|
||||
/// A list of callbacks that are called when a message is sent in order to add
|
||||
/// appropriate child elements.
|
||||
final List<MessageSendingCallback> _messageSendingCallbacks =
|
||||
List<MessageSendingCallback>.empty(growable: true);
|
||||
|
||||
void registerMessageSendingCallback(MessageSendingCallback callback) {
|
||||
_messageSendingCallbacks.add(callback);
|
||||
}
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessage,
|
||||
priority: -100,
|
||||
)
|
||||
priority: messageHandlerPriority,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza _,
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final message = state.stanza;
|
||||
final body = message.firstTag('body');
|
||||
|
||||
final hints = List<MessageProcessingHint>.empty(growable: true);
|
||||
for (final element
|
||||
in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
|
||||
hints.add(messageProcessingHintFromXml(element));
|
||||
final body = stanza.firstTag('body');
|
||||
if (body != null) {
|
||||
state.extensions.set(
|
||||
MessageBodyData(body.innerText()),
|
||||
);
|
||||
}
|
||||
|
||||
getAttributes().sendEvent(
|
||||
MessageEvent(
|
||||
body: body != null ? body.innerText() : '',
|
||||
fromJid: JID.fromString(message.attributes['from']! as String),
|
||||
toJid: JID.fromString(message.attributes['to']! as String),
|
||||
sid: message.attributes['id']! as String,
|
||||
stanzaId: state.stableId ?? const StableStanzaId(),
|
||||
isCarbon: state.isCarbon,
|
||||
deliveryReceiptRequested: state.deliveryReceiptRequested,
|
||||
isMarkable: state.isMarkable,
|
||||
type: message.attributes['type'] as String?,
|
||||
oob: state.oob,
|
||||
sfs: state.sfs,
|
||||
sims: state.sims,
|
||||
reply: state.reply,
|
||||
chatState: state.chatState,
|
||||
fun: state.fun,
|
||||
funReplacement: state.funReplacement,
|
||||
funCancellation: state.funCancellation,
|
||||
encrypted: state.encrypted,
|
||||
messageRetraction: state.messageRetraction,
|
||||
messageCorrectionId: state.lastMessageCorrectionSid,
|
||||
messageReactions: state.messageReactions,
|
||||
messageProcessingHints: hints.isEmpty ? null : hints,
|
||||
stickerPackId: state.stickerPackId,
|
||||
other: state.other,
|
||||
error: StanzaError.fromStanza(message),
|
||||
JID.fromString(state.stanza.attributes['from']! as String),
|
||||
JID.fromString(state.stanza.attributes['to']! as String),
|
||||
state.encrypted,
|
||||
state.extensions,
|
||||
id: state.stanza.attributes['id'] as String?,
|
||||
type: state.stanza.attributes['type'] as String?,
|
||||
error: StanzaError.fromStanza(state.stanza),
|
||||
encryptionError: state.encryptionError,
|
||||
),
|
||||
);
|
||||
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
/// Send a message to to with the content body. If deliveryRequest is true, then
|
||||
/// the message will also request a delivery receipt from the receiver.
|
||||
/// If id is non-null, then it will be the id of the message stanza.
|
||||
/// element to this id. If originId is non-null, then it will create an "origin-id"
|
||||
/// child in the message stanza and set its id to originId.
|
||||
void sendMessage(MessageDetails details) {
|
||||
assert(
|
||||
implies(
|
||||
details.quoteBody != null,
|
||||
details.quoteFrom != null && details.quoteId != null,
|
||||
/// Send an unawaitable message to [to]. [extensions] is a typed map that contains
|
||||
/// data for building the message.
|
||||
Future<void> sendMessage(
|
||||
JID to,
|
||||
TypedMap<StanzaHandlerExtension> extensions, {
|
||||
String type = 'chat',
|
||||
}) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.message(
|
||||
to: to.toString(),
|
||||
id: extensions.get<MessageIdData>()?.id,
|
||||
type: type,
|
||||
children: _messageSendingCallbacks
|
||||
.map((c) => c(extensions))
|
||||
.flattened
|
||||
.toList(),
|
||||
),
|
||||
extensions: extensions,
|
||||
awaitable: false,
|
||||
),
|
||||
'When quoting a message, then quoteFrom and quoteId must also be non-null',
|
||||
);
|
||||
}
|
||||
|
||||
final stanza = Stanza.message(
|
||||
to: details.to,
|
||||
type: 'chat',
|
||||
id: details.id,
|
||||
children: [],
|
||||
);
|
||||
|
||||
if (details.quoteBody != null) {
|
||||
final quote = QuoteData.fromBodies(details.quoteBody!, details.body!);
|
||||
|
||||
stanza
|
||||
..addChild(
|
||||
XMLNode(tag: 'body', text: quote.body),
|
||||
)
|
||||
..addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'reply',
|
||||
xmlns: replyXmlns,
|
||||
attributes: {'to': details.quoteFrom!, 'id': details.quoteId!},
|
||||
),
|
||||
)
|
||||
..addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'fallback',
|
||||
xmlns: fallbackXmlns,
|
||||
attributes: {'for': replyXmlns},
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'body',
|
||||
attributes: <String, String>{
|
||||
'start': '0',
|
||||
'end': '${quote.fallbackLength}',
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
var body = details.body;
|
||||
if (details.sfs != null && details.setOOBFallbackBody) {
|
||||
// TODO(Unknown): Maybe find a better solution
|
||||
final firstSource = details.sfs!.sources.first;
|
||||
if (firstSource is StatelessFileSharingUrlSource) {
|
||||
body = firstSource.url;
|
||||
} else if (firstSource is StatelessFileSharingEncryptedSource) {
|
||||
body = firstSource.source.url;
|
||||
}
|
||||
} else if (details.messageRetraction?.fallback != null) {
|
||||
body = details.messageRetraction!.fallback;
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
stanza.addChild(
|
||||
XMLNode(tag: 'body', text: body),
|
||||
);
|
||||
}
|
||||
List<XMLNode> _messageSendingCallback(
|
||||
TypedMap<StanzaHandlerExtension> extensions,
|
||||
) {
|
||||
if (extensions.get<ReplyData>() != null) {
|
||||
return [];
|
||||
}
|
||||
if (extensions.get<StickersData>() != null) {
|
||||
return [];
|
||||
}
|
||||
if (extensions.get<StatelessFileSharingData>() != null) {
|
||||
return [];
|
||||
}
|
||||
if (extensions.get<OOBData>() != null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (details.requestDeliveryReceipt) {
|
||||
stanza.addChild(makeMessageDeliveryRequest());
|
||||
}
|
||||
if (details.requestChatMarkers) {
|
||||
stanza.addChild(makeChatMarkerMarkable());
|
||||
}
|
||||
if (details.originId != null) {
|
||||
stanza.addChild(makeOriginIdElement(details.originId!));
|
||||
}
|
||||
final data = extensions.get<MessageBodyData>();
|
||||
return data != null ? [data.toXML()] : [];
|
||||
}
|
||||
|
||||
if (details.sfs != null) {
|
||||
stanza.addChild(details.sfs!.toXML());
|
||||
@override
|
||||
Future<void> postRegisterCallback() async {
|
||||
await super.postRegisterCallback();
|
||||
|
||||
final source = details.sfs!.sources.first;
|
||||
if (source is StatelessFileSharingUrlSource &&
|
||||
details.setOOBFallbackBody) {
|
||||
// SFS recommends OOB as a fallback
|
||||
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
|
||||
}
|
||||
}
|
||||
|
||||
if (details.chatState != null) {
|
||||
stanza.addChild(
|
||||
// TODO(Unknown): Move this into xep_0085.dart
|
||||
XMLNode.xmlns(
|
||||
tag: chatStateToString(details.chatState!),
|
||||
xmlns: chatStateXmlns,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (details.fun != null) {
|
||||
stanza.addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'file-upload',
|
||||
xmlns: fileUploadNotificationXmlns,
|
||||
children: [
|
||||
details.fun!.toXML(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (details.funReplacement != null) {
|
||||
stanza.addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'replaces',
|
||||
xmlns: fileUploadNotificationXmlns,
|
||||
attributes: <String, String>{
|
||||
'id': details.funReplacement!,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (details.messageRetraction != null) {
|
||||
stanza.addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'apply-to',
|
||||
xmlns: fasteningXmlns,
|
||||
attributes: <String, String>{
|
||||
'id': details.messageRetraction!.id,
|
||||
},
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'retract',
|
||||
xmlns: messageRetractionXmlns,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (details.messageRetraction!.fallback != null) {
|
||||
stanza.addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'fallback',
|
||||
xmlns: fallbackIndicationXmlns,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (details.lastMessageCorrectionId != null) {
|
||||
stanza.addChild(
|
||||
makeLastMessageCorrectionEdit(
|
||||
details.lastMessageCorrectionId!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (details.messageReactions != null) {
|
||||
stanza.addChild(details.messageReactions!.toXml());
|
||||
}
|
||||
|
||||
if (details.messageProcessingHints != null) {
|
||||
for (final hint in details.messageProcessingHints!) {
|
||||
stanza.addChild(hint.toXml());
|
||||
}
|
||||
}
|
||||
|
||||
if (details.stickerPackId != null) {
|
||||
stanza.addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'sticker',
|
||||
xmlns: stickersXmlns,
|
||||
attributes: {
|
||||
'pack': details.stickerPackId!,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getAttributes().sendStanza(stanza, awaitable: false);
|
||||
// Register the sending callback
|
||||
registerMessageSendingCallback(_messageSendingCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ const fullStanzaXmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas';
|
||||
// RFC 6121
|
||||
const rosterXmlns = 'jabber:iq:roster';
|
||||
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
|
||||
const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
|
||||
|
||||
// XEP-0004
|
||||
const dataFormsXmlns = 'jabber:x:data';
|
||||
const formVarFormType = 'FORM_TYPE';
|
||||
|
||||
// XEP-0030
|
||||
const discoInfoXmlns = 'http://jabber.org/protocol/disco#info';
|
||||
@@ -20,6 +22,11 @@ const discoItemsXmlns = 'http://jabber.org/protocol/disco#items';
|
||||
// XEP-0033
|
||||
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
|
||||
const vCardTempXmlns = 'vcard-temp';
|
||||
const vCardTempUpdate = 'vcard-temp:x:update';
|
||||
@@ -44,6 +51,9 @@ const userAvatarMetadataXmlns = 'urn:xmpp:avatar:metadata';
|
||||
// XEP-0085
|
||||
const chatStateXmlns = 'http://jabber.org/protocol/chatstates';
|
||||
|
||||
// XEP-0114
|
||||
const componentAcceptXmlns = 'jabber:component:accept';
|
||||
|
||||
// XEP-0115
|
||||
const capsXmlns = 'http://jabber.org/protocol/caps';
|
||||
|
||||
@@ -62,6 +72,9 @@ const delayedDeliveryXmlns = 'urn:xmpp:delay';
|
||||
// XEP-0234
|
||||
const jingleFileTransferXmlns = 'urn:xmpp:jingle:apps:file-transfer:5';
|
||||
|
||||
// XEP-0264
|
||||
const jingleContentThumbnailXmlns = 'urn:xmpp:thumbs:1';
|
||||
|
||||
// XEP-0280
|
||||
const carbonsXmlns = 'urn:xmpp:carbons:2';
|
||||
|
||||
@@ -71,12 +84,6 @@ const forwardedXmlns = 'urn:xmpp:forward:0';
|
||||
// XEP-0300
|
||||
const hashXmlns = 'urn:xmpp:hashes:2';
|
||||
const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names';
|
||||
const hashSha256 = 'sha-256';
|
||||
const hashSha512 = 'sha-512';
|
||||
const hashSha3256 = 'sha3-256';
|
||||
const hashSha3512 = 'sha3-512';
|
||||
const hashBlake2b256 = 'blake2b-256';
|
||||
const hashBlake2b512 = 'blake2b-512';
|
||||
|
||||
// XEP-0308
|
||||
const lmcXmlns = 'urn:xmpp:message-correct:0';
|
||||
@@ -99,7 +106,7 @@ const httpFileUploadXmlns = 'urn:xmpp:http:upload:0';
|
||||
// XEP-0372
|
||||
const referenceXmlns = 'urn:xmpp:reference:0';
|
||||
|
||||
// XEP-380
|
||||
// XEP-0380
|
||||
const emeXmlns = 'urn:xmpp:eme:0';
|
||||
const emeOtr = 'urn:xmpp:otr:0';
|
||||
const emeLegacyOpenPGP = 'jabber:x:encrypted';
|
||||
@@ -125,6 +132,9 @@ const sasl2Xmlns = 'urn:xmpp:sasl:2';
|
||||
// XEP-0420
|
||||
const sceXmlns = 'urn:xmpp:sce:1';
|
||||
|
||||
// XEP-0421
|
||||
const occupantIdXmlns = 'urn:xmpp:occupant-id:0';
|
||||
|
||||
// XEP-0422
|
||||
const fasteningXmlns = 'urn:xmpp:fasten:0';
|
||||
|
||||
@@ -156,7 +166,6 @@ const stickersXmlns = 'urn:xmpp:stickers:0';
|
||||
|
||||
// XEP-0461
|
||||
const replyXmlns = 'urn:xmpp:reply:0';
|
||||
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
||||
|
||||
// ???
|
||||
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -11,3 +11,4 @@ const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
|
||||
const bind2Negotiator = 'org.moxxmpp.bind2';
|
||||
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
||||
const carbonsNegotiator = 'org.moxxmpp.bind2.carbons';
|
||||
const presenceNegotiator = 'org.moxxmpp.core.presence';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.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/socket.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
/// The state a negotiator is currently in
|
||||
enum NegotiatorState {
|
||||
@@ -117,8 +117,7 @@ abstract class XmppFeatureNegotiatorBase {
|
||||
/// Returns true if a feature in [features], which are the children of the
|
||||
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
return firstWhereOrNull(
|
||||
features,
|
||||
return features.firstWhereOrNull(
|
||||
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
||||
) !=
|
||||
null;
|
||||
|
||||
169
packages/moxxmpp/lib/src/parser.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:xml/src/xml_events/utils/conversion_sink.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
import 'package:xml/xml_events.dart';
|
||||
|
||||
/// A result object for XmlStreamBuffer.
|
||||
abstract class XMPPStreamObject {}
|
||||
|
||||
/// A complete XML element returned by the stream buffer.
|
||||
class XMPPStreamElement extends XMPPStreamObject {
|
||||
XMPPStreamElement(this.node);
|
||||
|
||||
/// The actual [XMLNode].
|
||||
final XMLNode node;
|
||||
}
|
||||
|
||||
/// Just the stream header of a new XML stream.
|
||||
class XMPPStreamHeader extends XMPPStreamObject {
|
||||
XMPPStreamHeader(this.attributes);
|
||||
|
||||
/// The headers of the stream header.
|
||||
final Map<String, String> attributes;
|
||||
}
|
||||
|
||||
/// A wrapper around a [Converter]'s [Converter.startChunkedConversion] method.
|
||||
class _ChunkedConversionBuffer<S, T> {
|
||||
/// Use the converter [converter].
|
||||
_ChunkedConversionBuffer(Converter<S, List<T>> converter) {
|
||||
_outputSink = ConversionSink<List<T>>(_results.addAll);
|
||||
_inputSink = converter.startChunkedConversion(_outputSink);
|
||||
}
|
||||
|
||||
/// The results of the converter.
|
||||
final List<T> _results = List<T>.empty(growable: true);
|
||||
|
||||
/// The sink that outputs to [_results].
|
||||
late ConversionSink<List<T>> _outputSink;
|
||||
|
||||
/// The sink that we use for input.
|
||||
late Sink<S> _inputSink;
|
||||
|
||||
/// Close all opened sinks.
|
||||
void close() {
|
||||
_inputSink.close();
|
||||
_outputSink.close();
|
||||
}
|
||||
|
||||
/// Turn the input [input] into a list of [T] according to the initial converter.
|
||||
List<T> convert(S input) {
|
||||
_results.clear();
|
||||
_inputSink.add(input);
|
||||
return _results;
|
||||
}
|
||||
}
|
||||
|
||||
/// A buffer to put between a socket's input and a full XML stream.
|
||||
class XMPPStreamParser
|
||||
extends StreamTransformerBase<String, List<XMPPStreamObject>> {
|
||||
final StreamController<List<XMPPStreamObject>> _streamController =
|
||||
StreamController<List<XMPPStreamObject>>();
|
||||
|
||||
/// Turns a String into a list of [XmlEvent]s in a chunked fashion.
|
||||
_ChunkedConversionBuffer<String, XmlEvent> _eventBuffer =
|
||||
_ChunkedConversionBuffer<String, XmlEvent>(XmlEventDecoder());
|
||||
|
||||
/// Turns a list of [XmlEvent]s into a list of [XmlNode]s in a chunked fashion.
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlNode> _childBuffer =
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlNode>(const XmlNodeDecoder());
|
||||
|
||||
/// The selectors.
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlEvent> _childSelector =
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlEvent>(
|
||||
XmlSubtreeSelector((event) => event.qualifiedName != 'stream:stream'),
|
||||
);
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlEvent> _streamHeaderSelector =
|
||||
_ChunkedConversionBuffer<List<XmlEvent>, XmlEvent>(
|
||||
XmlSubtreeSelector((event) => event.qualifiedName == 'stream:stream'),
|
||||
);
|
||||
|
||||
void reset() {
|
||||
try {
|
||||
_eventBuffer.close();
|
||||
} catch (_) {
|
||||
// Do nothing. A crash here may indicate that we end on invalid XML, which is fine
|
||||
// since we're not going to use the buffer's output anymore.
|
||||
}
|
||||
try {
|
||||
_childBuffer.close();
|
||||
} catch (_) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
_childSelector.close();
|
||||
} catch (_) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
_streamHeaderSelector.close();
|
||||
} catch (_) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Recreate the buffers.
|
||||
_eventBuffer =
|
||||
_ChunkedConversionBuffer<String, XmlEvent>(XmlEventDecoder());
|
||||
_childBuffer = _ChunkedConversionBuffer<List<XmlEvent>, XmlNode>(
|
||||
const XmlNodeDecoder(),
|
||||
);
|
||||
_childSelector = _ChunkedConversionBuffer<List<XmlEvent>, XmlEvent>(
|
||||
XmlSubtreeSelector((event) => event.qualifiedName != 'stream:stream'),
|
||||
);
|
||||
_streamHeaderSelector = _ChunkedConversionBuffer<List<XmlEvent>, XmlEvent>(
|
||||
XmlSubtreeSelector((event) => event.qualifiedName == 'stream:stream'),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<XMPPStreamObject>> bind(Stream<String> stream) {
|
||||
// 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
|
||||
// XML parser whenever we start a new connection.
|
||||
stream.listen((input) {
|
||||
final events = _eventBuffer.convert(input);
|
||||
final streamHeaderEvents = _streamHeaderSelector.convert(events);
|
||||
final objects = List<XMPPStreamObject>.empty(growable: true);
|
||||
|
||||
// Process the stream header separately.
|
||||
for (final event in streamHeaderEvents) {
|
||||
if (event is! XmlStartElementEvent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.name != 'stream:stream') {
|
||||
continue;
|
||||
}
|
||||
|
||||
objects.add(
|
||||
XMPPStreamHeader(
|
||||
Map<String, String>.fromEntries(
|
||||
event.attributes.map((attr) {
|
||||
return MapEntry(attr.name, attr.value);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Process the children of the <stream:stream> element.
|
||||
final childEvents = _childSelector.convert(events);
|
||||
final children = _childBuffer.convert(childEvents);
|
||||
for (final node in children) {
|
||||
if (node.nodeType == XmlNodeType.ELEMENT) {
|
||||
objects.add(
|
||||
XMPPStreamElement(
|
||||
XMLNode.fromXmlElement(node as XmlElement),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_streamController.add(objects);
|
||||
});
|
||||
|
||||
return _streamController.stream;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/src/connection.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
@@ -8,15 +8,44 @@ import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.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
|
||||
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||
/// presence.
|
||||
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
||||
|
||||
/// A pseudo-negotiator that does not really negotiate anything. Instead, its purpose
|
||||
/// is to look for a stream feature indicating that we can pre-approve subscription
|
||||
/// requests, shown by [PresenceNegotiator.preApprovalSupported].
|
||||
class PresenceNegotiator extends XmppFeatureNegotiatorBase {
|
||||
PresenceNegotiator()
|
||||
: super(11, false, subscriptionPreApprovalXmlns, presenceNegotiator);
|
||||
|
||||
/// Flag indicating whether presence subscription pre-approval is supported
|
||||
bool _supported = false;
|
||||
bool get preApprovalSupported => _supported;
|
||||
|
||||
@override
|
||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||
XMLNode nonza,
|
||||
) async {
|
||||
_supported = true;
|
||||
return const Result(NegotiatorState.done);
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_supported = false;
|
||||
|
||||
super.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// A mandatory manager that handles initial presence sending, sending of subscription
|
||||
/// request management requests and triggers events for incoming presence stanzas.
|
||||
class PresenceManager extends XmppManagerBase {
|
||||
@@ -26,12 +55,18 @@ class PresenceManager extends XmppManagerBase {
|
||||
final List<PresencePreSendCallback> _presenceCallbacks =
|
||||
List.empty(growable: true);
|
||||
|
||||
/// The priority of the presence handler. If a handler should run before this one,
|
||||
/// which terminates processing, make sure the handler has a priority greater than
|
||||
/// [presenceHandlerPriority].
|
||||
static int presenceHandlerPriority = -100;
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'presence',
|
||||
callback: _onPresence,
|
||||
)
|
||||
priority: presenceHandlerPriority,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -49,12 +84,8 @@ class PresenceManager extends XmppManagerBase {
|
||||
Future<void> onXmppEvent(XmppEvent event) async {
|
||||
if (event is StreamNegotiationsDoneEvent) {
|
||||
// Send initial presence only when we have not resumed the stream
|
||||
final sm = getAttributes().getNegotiatorById<StreamManagementNegotiator>(
|
||||
streamManagementNegotiator,
|
||||
);
|
||||
final isResumed = sm?.isResumed ?? false;
|
||||
if (!isResumed) {
|
||||
unawaited(sendInitialPresence());
|
||||
if (!event.resumed) {
|
||||
await sendInitialPresence();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +104,7 @@ class PresenceManager extends XmppManagerBase {
|
||||
from: JID.fromString(presence.from!),
|
||||
),
|
||||
);
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -82,10 +113,7 @@ class PresenceManager extends XmppManagerBase {
|
||||
if (presence.from != null) {
|
||||
logger.finest("Received presence from '${presence.from}'");
|
||||
|
||||
getAttributes().sendEvent(
|
||||
PresenceReceivedEvent(JID.fromString(presence.from!), presence),
|
||||
);
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -107,65 +135,112 @@ class PresenceManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final attrs = getAttributes();
|
||||
attrs.sendNonza(
|
||||
Stanza.presence(
|
||||
from: attrs.getFullJID().toString(),
|
||||
children: children,
|
||||
await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
children: children,
|
||||
),
|
||||
awaitable: false,
|
||||
addId: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Send an unavailable presence with no 'to' attribute.
|
||||
void sendUnavailablePresence() {
|
||||
getAttributes().sendStanza(
|
||||
Stanza.presence(
|
||||
type: 'unavailable',
|
||||
Future<void> sendUnavailablePresence() async {
|
||||
// Bypass the queue so that this get's sent immediately.
|
||||
// If we do it like this, we can also block the disconnection
|
||||
// until we're actually ready.
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'unavailable',
|
||||
),
|
||||
awaitable: false,
|
||||
bypassQueue: true,
|
||||
postSendExtensions: TypedMap<StanzaHandlerExtension>.fromList([
|
||||
const StreamManagementData(true, null),
|
||||
]),
|
||||
),
|
||||
addFrom: StanzaFromType.full,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sends a subscription request to [to].
|
||||
void sendSubscriptionRequest(String to) {
|
||||
getAttributes().sendStanza(
|
||||
Stanza.presence(
|
||||
type: 'subscribe',
|
||||
to: to,
|
||||
/// Similar to [requestSubscription], but it also tells the server to automatically
|
||||
/// accept a subscription request from [to], should it arrive.
|
||||
/// This requires a [PresenceNegotiator] to be registered as this feature is optional.
|
||||
///
|
||||
/// Returns true, when the stanza was sent. Returns false, when the stanza was not sent,
|
||||
/// for example because the server does not support subscription pre-approvals.
|
||||
Future<bool> preApproveSubscription(JID to) async {
|
||||
final negotiator = getAttributes()
|
||||
.getNegotiatorById<PresenceNegotiator>(presenceNegotiator);
|
||||
assert(negotiator != null, 'No PresenceNegotiator registered');
|
||||
|
||||
if (!negotiator!.preApprovalSupported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'subscribed',
|
||||
to: to.toString(),
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Sends a subscription request to [to].
|
||||
Future<void> requestSubscription(JID to) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'subscribe',
|
||||
to: to.toString(),
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Accept a subscription request from [to].
|
||||
Future<void> acceptSubscriptionRequest(JID to) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'subscribed',
|
||||
to: to.toString(),
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Send a subscription request rejection to [to].
|
||||
Future<void> rejectSubscriptionRequest(JID to) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'unsubscribed',
|
||||
to: to.toString(),
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
addFrom: StanzaFromType.none,
|
||||
);
|
||||
}
|
||||
|
||||
/// Sends an unsubscription request to [to].
|
||||
void sendUnsubscriptionRequest(String to) {
|
||||
getAttributes().sendStanza(
|
||||
Stanza.presence(
|
||||
type: 'unsubscribe',
|
||||
to: to,
|
||||
Future<void> unsubscribe(JID to) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
type: 'unsubscribe',
|
||||
to: to.toString(),
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
addFrom: StanzaFromType.none,
|
||||
);
|
||||
}
|
||||
|
||||
/// Accept a presence subscription request for [to].
|
||||
void sendSubscriptionRequestApproval(String to) {
|
||||
getAttributes().sendStanza(
|
||||
Stanza.presence(
|
||||
type: 'subscribed',
|
||||
to: to,
|
||||
),
|
||||
addFrom: StanzaFromType.none,
|
||||
);
|
||||
}
|
||||
|
||||
/// Reject a presence subscription request for [to].
|
||||
void sendSubscriptionRequestRejection(String to) {
|
||||
getAttributes().sendStanza(
|
||||
Stanza.presence(
|
||||
type: 'unsubscribed',
|
||||
to: to,
|
||||
),
|
||||
addFrom: StanzaFromType.none,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ abstract class ReconnectionPolicy {
|
||||
PerformReconnectFunction? performReconnect;
|
||||
|
||||
final Lock _lock = Lock();
|
||||
|
||||
|
||||
/// Indicate if a reconnection attempt is currently running.
|
||||
bool _isReconnecting = false;
|
||||
|
||||
@@ -25,17 +25,19 @@ abstract class ReconnectionPolicy {
|
||||
bool _shouldAttemptReconnection = false;
|
||||
|
||||
@protected
|
||||
Future<bool> canTryReconnecting() async => _lock.synchronized(() => !_isReconnecting);
|
||||
Future<bool> canTryReconnecting() async =>
|
||||
_lock.synchronized(() => !_isReconnecting);
|
||||
|
||||
@protected
|
||||
Future<bool> getIsReconnecting() async => _lock.synchronized(() => _isReconnecting);
|
||||
Future<bool> getIsReconnecting() async =>
|
||||
_lock.synchronized(() => _isReconnecting);
|
||||
|
||||
Future<void> _resetIsReconnecting() async {
|
||||
await _lock.synchronized(() {
|
||||
_isReconnecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// Called by XmppConnection to register the policy.
|
||||
void register(
|
||||
PerformReconnectFunction performReconnect,
|
||||
@@ -62,10 +64,10 @@ abstract class ReconnectionPolicy {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// Called by the XmppConnection when the reconnection failed.
|
||||
Future<void> onFailure() async {}
|
||||
|
||||
|
||||
/// Caled by the XmppConnection when the reconnection was successful.
|
||||
Future<void> onSuccess();
|
||||
|
||||
@@ -75,8 +77,7 @@ abstract class ReconnectionPolicy {
|
||||
|
||||
/// Set whether a reconnection attempt should be made.
|
||||
Future<void> setShouldReconnect(bool value) async {
|
||||
return _lock
|
||||
.synchronized(() => _shouldAttemptReconnection = value);
|
||||
return _lock.synchronized(() => _shouldAttemptReconnection = value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,35 +107,51 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||
final Logger _log = Logger('RandomBackoffReconnectionPolicy');
|
||||
|
||||
final Lock _timerLock = Lock();
|
||||
|
||||
|
||||
/// Called when the backoff expired
|
||||
@visibleForTesting
|
||||
Future<void> onTimerElapsed() async {
|
||||
_log.fine('Timer elapsed. Waiting for lock...');
|
||||
await _timerLock.synchronized(() async {
|
||||
_log.finest('Timer elapsed. Waiting for lock...');
|
||||
final shouldContinue = await _timerLock.synchronized(() async {
|
||||
_log.finest('Timer lock aquired');
|
||||
if (_timer == null) {
|
||||
_log.finest(
|
||||
'The timer is already set to null. Doing nothing.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await getIsReconnecting())) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await getShouldReconnect())) {
|
||||
_log.fine(
|
||||
'Should not reconnect. Stopping here.',
|
||||
_log.finest(
|
||||
'Should not reconnect. Stopping here.',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_log.fine('Triggering reconnect');
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
await performReconnect!();
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!shouldContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
_log.fine('Triggering reconnect');
|
||||
await performReconnect!();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reset() async {
|
||||
_log.finest('Resetting internal state');
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
});
|
||||
await super.reset();
|
||||
}
|
||||
|
||||
@@ -143,11 +160,13 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||
final seconds =
|
||||
Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
|
||||
_log.finest('Failure occured. Starting random backoff with ${seconds}s');
|
||||
_timer?.cancel();
|
||||
|
||||
_timer = Timer(Duration(seconds: seconds), onTimerElapsed);
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: seconds), onTimerElapsed);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<void> onSuccess() async {
|
||||
await reset();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.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: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/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
@@ -13,15 +13,13 @@ abstract class SaslNegotiator extends XmppFeatureNegotiatorBase {
|
||||
@override
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
// Is SASL advertised?
|
||||
final mechanisms = firstWhereOrNull(
|
||||
features,
|
||||
final mechanisms = features.firstWhereOrNull(
|
||||
(XMLNode feature) => feature.attributes['xmlns'] == saslXmlns,
|
||||
);
|
||||
if (mechanisms == null) return false;
|
||||
|
||||
// Is SASL PLAIN advertised?
|
||||
return firstWhereOrNull(
|
||||
mechanisms.children,
|
||||
return mechanisms.children.firstWhereOrNull(
|
||||
(XMLNode mechanism) => mechanism.text == mechanismName,
|
||||
) !=
|
||||
null;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.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/nonza.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/xep_0388.dart';
|
||||
import 'package:saslprep/saslprep.dart';
|
||||
@@ -96,7 +96,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
state = NegotiatorState.done;
|
||||
if (pickedForSasl2) {
|
||||
state = NegotiatorState.done;
|
||||
}
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/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/nonza.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/xep_0388.dart';
|
||||
import 'package:random_string/random_string.dart';
|
||||
@@ -246,6 +246,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
bool _checkSignature(String base64Signature) {
|
||||
final signature =
|
||||
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
||||
_log.finest(
|
||||
'Expecting signature: "$_serverSignature", got: "${signature["v"]}"',
|
||||
);
|
||||
return signature['v']! == _serverSignature;
|
||||
}
|
||||
|
||||
@@ -360,6 +363,11 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
|
||||
@override
|
||||
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
|
||||
// signature.
|
||||
state = NegotiatorState.done;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
enum _StartTlsState { ready, requested }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/attributes.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/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
|
||||
@immutable
|
||||
class XmppRosterItem {
|
||||
@@ -122,7 +122,7 @@ class RosterManager extends XmppManagerBase {
|
||||
tagName: 'query',
|
||||
tagXmlns: rosterXmlns,
|
||||
callback: _onRosterPush,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -145,7 +145,7 @@ class RosterManager extends XmppManagerBase {
|
||||
logger.warning(
|
||||
'Roster push invalid! Unexpected from attribute: ${stanza.toXml()}',
|
||||
);
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
final query = stanza.firstTag('query', xmlns: rosterXmlns)!;
|
||||
@@ -154,7 +154,7 @@ class RosterManager extends XmppManagerBase {
|
||||
|
||||
if (item == null) {
|
||||
logger.warning('Received empty roster push');
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
unawaited(
|
||||
@@ -177,13 +177,23 @@ class RosterManager extends XmppManagerBase {
|
||||
[],
|
||||
);
|
||||
|
||||
return state.copyWith(done: true);
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
/// Shared code between requesting rosters without and with roster versioning, if
|
||||
/// 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(
|
||||
XMLNode? query,
|
||||
String? requestedRosterVersion,
|
||||
) async {
|
||||
final List<XmppRosterItem> items;
|
||||
String? rosterVersion;
|
||||
@@ -204,6 +214,14 @@ class RosterManager extends XmppManagerBase {
|
||||
.toList();
|
||||
|
||||
rosterVersion = query.attributes['ver'] as String?;
|
||||
} else if (requestedRosterVersion != null) {
|
||||
// Skip the handleRosterFetch call since nothing changed.
|
||||
return Result(
|
||||
RosterRequestResult(
|
||||
[],
|
||||
requestedRosterVersion,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
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',
|
||||
@@ -223,26 +241,34 @@ class RosterManager extends XmppManagerBase {
|
||||
return Result(result);
|
||||
}
|
||||
|
||||
/// Requests the roster following RFC 6121.
|
||||
Future<Result<RosterRequestResult, RosterError>> requestRoster() async {
|
||||
/// Requests the roster following RFC 6121. If [useRosterVersion] is set to false, then
|
||||
/// roster versioning will not be used, even if the server supports it and we have a last
|
||||
/// known roster version.
|
||||
Future<Result<RosterRequestResult, RosterError>> requestRoster({
|
||||
bool useRosterVersion = true,
|
||||
}) async {
|
||||
final attrs = getAttributes();
|
||||
final query = XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
);
|
||||
final rosterVersion = await _stateManager.getRosterVersion();
|
||||
if (rosterVersion != null && rosterVersioningAvailable()) {
|
||||
if (rosterVersion != null &&
|
||||
rosterVersioningAvailable() &&
|
||||
useRosterVersion) {
|
||||
query.attributes['ver'] = rosterVersion;
|
||||
}
|
||||
|
||||
final response = await attrs.sendStanza(
|
||||
Stanza.iq(
|
||||
type: 'get',
|
||||
children: [
|
||||
query,
|
||||
],
|
||||
final response = (await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
type: 'get',
|
||||
children: [
|
||||
query,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
))!;
|
||||
|
||||
if (response.attributes['type'] != 'result') {
|
||||
logger.warning('Error requesting roster: ${response.toXml()}');
|
||||
@@ -250,7 +276,7 @@ class RosterManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -258,20 +284,23 @@ class RosterManager extends XmppManagerBase {
|
||||
Future<Result<RosterRequestResult?, RosterError>>
|
||||
requestRosterPushes() async {
|
||||
final attrs = getAttributes();
|
||||
final result = await attrs.sendStanza(
|
||||
Stanza.iq(
|
||||
type: 'get',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
attributes: {
|
||||
'ver': await _stateManager.getRosterVersion() ?? '',
|
||||
},
|
||||
)
|
||||
],
|
||||
final rosterVersion = await _stateManager.getRosterVersion();
|
||||
final result = (await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
type: 'get',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
attributes: {
|
||||
'ver': rosterVersion ?? '',
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
))!;
|
||||
|
||||
if (result.attributes['type'] != 'result') {
|
||||
logger.warning('Requesting roster pushes failed: ${result.toXml()}');
|
||||
@@ -279,7 +308,7 @@ class RosterManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final query = result.firstTag('query', xmlns: rosterXmlns);
|
||||
return _handleRosterResponse(query);
|
||||
return _handleRosterResponse(query, rosterVersion);
|
||||
}
|
||||
|
||||
bool rosterVersioningAvailable() {
|
||||
@@ -296,31 +325,31 @@ class RosterManager extends XmppManagerBase {
|
||||
List<String>? groups,
|
||||
}) async {
|
||||
final attrs = getAttributes();
|
||||
final response = await attrs.sendStanza(
|
||||
Stanza.iq(
|
||||
type: 'set',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{
|
||||
'jid': jid,
|
||||
...title == jid.split('@')[0]
|
||||
? <String, String>{}
|
||||
: <String, String>{'name': title}
|
||||
},
|
||||
children: (groups ?? [])
|
||||
.map((group) => XMLNode(tag: 'group', text: group))
|
||||
.toList(),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
final response = (await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
type: 'set',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{
|
||||
'jid': jid,
|
||||
if (title == jid.split('@')[0]) 'name': title,
|
||||
},
|
||||
children: (groups ?? [])
|
||||
.map((group) => XMLNode(tag: 'group', text: group))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
))!;
|
||||
|
||||
if (response.attributes['type'] != 'result') {
|
||||
logger.severe('Error adding $jid to roster: $response');
|
||||
@@ -334,26 +363,28 @@ class RosterManager extends XmppManagerBase {
|
||||
/// false otherwise.
|
||||
Future<RosterRemovalResult> removeFromRoster(String jid) async {
|
||||
final attrs = getAttributes();
|
||||
final response = await attrs.sendStanza(
|
||||
Stanza.iq(
|
||||
type: 'set',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{
|
||||
'jid': jid,
|
||||
'subscription': 'remove'
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
final response = (await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
type: 'set',
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: {
|
||||
'jid': jid,
|
||||
'subscription': 'remove',
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
))!;
|
||||
|
||||
if (response.attributes['type'] != 'result') {
|
||||
logger.severe('Failed to remove roster item: ${response.toXml()}');
|
||||
|
||||
@@ -4,14 +4,22 @@ class ConnectionSettings {
|
||||
ConnectionSettings({
|
||||
required this.jid,
|
||||
required this.password,
|
||||
required this.useDirectTLS,
|
||||
this.host,
|
||||
this.port,
|
||||
});
|
||||
final JID jid;
|
||||
final String password;
|
||||
final bool useDirectTLS;
|
||||
|
||||
/// The JID to authenticate as.
|
||||
final JID jid;
|
||||
|
||||
/// The password to use during authentication.
|
||||
final String password;
|
||||
|
||||
/// The host to connect to. Skips DNS resolution if specified.
|
||||
final String? host;
|
||||
|
||||
/// The port to connect to. Skips DNS resolution if specified.
|
||||
final int? port;
|
||||
|
||||
/// The JID of the server we're connected to.
|
||||
JID get serverJid => JID('', jid.domain, '');
|
||||
}
|
||||
|
||||
@@ -1,28 +1,109 @@
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
|
||||
/// A simple description of the <error /> element that may be inside a stanza
|
||||
class StanzaError {
|
||||
StanzaError(this.type, this.error);
|
||||
String type;
|
||||
String error;
|
||||
/// A description of a stanza to send.
|
||||
class StanzaDetails {
|
||||
const StanzaDetails(
|
||||
this.stanza, {
|
||||
this.extensions,
|
||||
this.addId = true,
|
||||
this.awaitable = true,
|
||||
this.shouldEncrypt = true,
|
||||
this.encrypted = false,
|
||||
this.forceEncryption = false,
|
||||
this.bypassQueue = false,
|
||||
this.postSendExtensions,
|
||||
});
|
||||
|
||||
/// The stanza to send.
|
||||
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.
|
||||
final bool addId;
|
||||
|
||||
/// Track the stanza to allow awaiting its response.
|
||||
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;
|
||||
|
||||
/// 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
|
||||
/// now, where it's okay if it does not get sent.
|
||||
/// This should never have to be set to true.
|
||||
final bool bypassQueue;
|
||||
|
||||
/// This makes the Stream Management implementation, when available, ignore the stanza,
|
||||
/// meaning that it gets counted but excluded from resending.
|
||||
/// This should never have to be set to true.
|
||||
final TypedMap<StanzaHandlerExtension>? postSendExtensions;
|
||||
}
|
||||
|
||||
/// A general error type for errors.
|
||||
abstract class StanzaError {
|
||||
static StanzaError? fromXMLNode(XMLNode node) {
|
||||
final error = node.firstTag('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) {
|
||||
final error = stanza.firstTag('error');
|
||||
if (error == null) return null;
|
||||
|
||||
final stanzaError = error.firstTagByXmlns(fullStanzaXmlns);
|
||||
if (stanzaError == null) return null;
|
||||
|
||||
return StanzaError(
|
||||
error.attributes['type']! as String,
|
||||
stanzaError.tag,
|
||||
);
|
||||
return fromXMLNode(stanza);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// ignore: use_super_parameters
|
||||
Stanza({
|
||||
@@ -33,6 +114,7 @@ class Stanza extends XMLNode {
|
||||
List<XMLNode> children = const [],
|
||||
required String tag,
|
||||
Map<String, String> attributes = const {},
|
||||
String? xmlns,
|
||||
}) : super(
|
||||
tag: tag,
|
||||
attributes: <String, dynamic>{
|
||||
@@ -45,7 +127,7 @@ class Stanza extends XMLNode {
|
||||
...from != null
|
||||
? <String, dynamic>{'from': from}
|
||||
: <String, dynamic>{},
|
||||
'xmlns': stanzaXmlns
|
||||
if (xmlns != null) 'xmlns': xmlns,
|
||||
},
|
||||
children: children,
|
||||
);
|
||||
@@ -57,6 +139,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'iq',
|
||||
@@ -64,8 +147,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,6 +160,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'presence',
|
||||
@@ -83,8 +168,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
factory Stanza.message({
|
||||
@@ -94,6 +180,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'message',
|
||||
@@ -101,8 +188,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,18 +218,23 @@ class Stanza extends XMLNode {
|
||||
|
||||
Stanza copyWith({
|
||||
String? id,
|
||||
String? from,
|
||||
Object? from = _stanzaNotDefined,
|
||||
String? to,
|
||||
String? type,
|
||||
List<XMLNode>? children,
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: tag,
|
||||
to: to ?? this.to,
|
||||
from: from ?? this.from,
|
||||
from: from != _stanzaNotDefined ? from as String? : this.from,
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
children: children ?? this.children,
|
||||
attributes: {
|
||||
...attributes.cast<String, String>(),
|
||||
},
|
||||
xmlns: xmlns ?? this.xmlns,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -157,15 +250,14 @@ XMLNode buildErrorElement(String type, String condition, {String? text}) {
|
||||
XMLNode.xmlns(
|
||||
tag: condition,
|
||||
xmlns: fullStanzaXmlns,
|
||||
children: text != null
|
||||
? [
|
||||
XMLNode.xmlns(
|
||||
tag: 'text',
|
||||
xmlns: fullStanzaXmlns,
|
||||
text: text,
|
||||
)
|
||||
]
|
||||
: [],
|
||||
children: [
|
||||
if (text != null)
|
||||
XMLNode.xmlns(
|
||||
tag: 'text',
|
||||
xmlns: fullStanzaXmlns,
|
||||
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
@@ -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);
|
||||
}
|
||||
}
|
||||
5
packages/moxxmpp/lib/src/util/list.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
extension ListItemCountExtension<T> on List<T> {
|
||||
int count(bool Function(T) matches) {
|
||||
return where(matches).length;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,61 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// A job to be submitted to an [AsyncQueue].
|
||||
typedef AsyncQueueJob = Future<void> Function();
|
||||
class StanzaQueueEntry {
|
||||
const StanzaQueueEntry(
|
||||
this.details,
|
||||
this.completer,
|
||||
);
|
||||
|
||||
/// The actual data to send.
|
||||
final StanzaDetails details;
|
||||
|
||||
/// The [Completer] to resolve when the response is received.
|
||||
final Completer<XMLNode>? completer;
|
||||
}
|
||||
|
||||
/// A function that is executed when a job is popped from the queue.
|
||||
typedef SendStanzaFunction = Future<void> Function(StanzaQueueEntry);
|
||||
|
||||
/// A function that is called before popping a queue item. Should return true when
|
||||
/// the [SendStanzaFunction] can be executed.
|
||||
typedef CanSendCallback = Future<bool> Function();
|
||||
|
||||
/// A (hopefully) async-safe queue that attempts to force
|
||||
/// in-order execution of its jobs.
|
||||
class AsyncQueue {
|
||||
/// The lock for accessing [AsyncQueue._lock] and [AsyncQueue._running].
|
||||
class AsyncStanzaQueue {
|
||||
AsyncStanzaQueue(
|
||||
this._sendStanzaFunction,
|
||||
this._canSendCallback,
|
||||
);
|
||||
|
||||
/// The lock for accessing [AsyncStanzaQueue._queue].
|
||||
final Lock _lock = Lock();
|
||||
|
||||
/// The actual job queue.
|
||||
final Queue<AsyncQueueJob> _queue = Queue<AsyncQueueJob>();
|
||||
final Queue<StanzaQueueEntry> _queue = Queue<StanzaQueueEntry>();
|
||||
|
||||
/// Indicates whether we are currently executing a job.
|
||||
bool _running = false;
|
||||
/// Sends the stanza when we can pop from the queue.
|
||||
final SendStanzaFunction _sendStanzaFunction;
|
||||
|
||||
final CanSendCallback _canSendCallback;
|
||||
|
||||
@visibleForTesting
|
||||
Queue<AsyncQueueJob> get queue => _queue;
|
||||
Queue<StanzaQueueEntry> get queue => _queue;
|
||||
|
||||
@visibleForTesting
|
||||
bool get isRunning => _running;
|
||||
/// Adds a job [entry] to the queue.
|
||||
Future<void> enqueueStanza(StanzaQueueEntry entry) async {
|
||||
await _lock.synchronized(() async {
|
||||
_queue.add(entry);
|
||||
|
||||
/// Adds a job [job] to the queue.
|
||||
Future<void> addJob(AsyncQueueJob job) async {
|
||||
await _lock.synchronized(() {
|
||||
_queue.add(job);
|
||||
|
||||
if (!_running && _queue.isNotEmpty) {
|
||||
_running = true;
|
||||
unawaited(_popJob());
|
||||
if (_queue.isNotEmpty && await _canSendCallback()) {
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -40,16 +64,26 @@ class AsyncQueue {
|
||||
await _lock.synchronized(_queue.clear);
|
||||
}
|
||||
|
||||
Future<void> _popJob() async {
|
||||
final job = _queue.removeFirst();
|
||||
final future = job();
|
||||
await future;
|
||||
Future<void> _runJob(StanzaQueueEntry details) async {
|
||||
await _sendStanzaFunction(details);
|
||||
|
||||
await _lock.synchronized(() async {
|
||||
if (_queue.isNotEmpty && await _canSendCallback()) {
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> restart() async {
|
||||
if (!(await _canSendCallback())) return;
|
||||
|
||||
await _lock.synchronized(() {
|
||||
if (_queue.isNotEmpty) {
|
||||
unawaited(_popJob());
|
||||
} else {
|
||||
_running = false;
|
||||
unawaited(
|
||||
_runJob(_queue.removeFirst()),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
25
packages/moxxmpp/lib/src/util/typed_map.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
/// A map, similar to Map, but always uses the type of the value as the key.
|
||||
class TypedMap<B> {
|
||||
/// Create an empty typed map.
|
||||
TypedMap();
|
||||
|
||||
/// Create a typed map from a list of values.
|
||||
TypedMap.fromList(List<B> items) {
|
||||
for (final item in items) {
|
||||
_data[item.runtimeType] = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal mapping of type -> data
|
||||
final Map<Object, B> _data = {};
|
||||
|
||||
/// Associate the type of [value] with [value] in the map.
|
||||
void set<T extends B>(T value) {
|
||||
_data[T] = value;
|
||||
}
|
||||
|
||||
/// Return the object of type [T] from the map, if it has been stored.
|
||||
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],
|
||||
);
|
||||
}
|
||||