Compare commits
213
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
275d6e0346 | ||
|
|
0d9afd546c | ||
|
|
3da334b5cf | ||
|
|
2947e2c539 | ||
|
|
ac8433f51f | ||
|
|
808371b271 | ||
|
|
7fdd83ea69 | ||
|
|
68e2a65dcf | ||
|
|
d977a74446 | ||
|
|
29f0419154 | ||
|
|
b354ca8d0a | ||
|
|
ec6b5ab753 | ||
|
|
ce1815d1f3 | ||
|
|
fbb495dc2f | ||
|
|
4a6aa79e56 | ||
|
|
0033d0eb6e | ||
|
|
24cb05f91b | ||
|
|
91f763ac26 | ||
|
|
51edb61443 | ||
|
|
4e01d32e90 | ||
|
|
f2fe06104c | ||
|
|
89fe8f0a9c | ||
|
|
9358175925 | ||
|
|
564a237986 | ||
|
|
cf425917cf | ||
|
|
63b7abd6f9 | ||
|
|
f460e5ebe9 | ||
|
|
af8bc606d6 | ||
|
|
30482c86f0 | ||
|
|
f86dbe6af8 | ||
|
|
478b5b8770 | ||
|
|
7ab3f4f0d9 | ||
|
|
2e60e9841e | ||
|
|
52ad9a7ddb | ||
|
|
ac5e0c13b7 | ||
|
|
b49658784b | ||
|
|
d4a972e073 | ||
|
|
1009a2f967 | ||
|
|
f355f01fc8 | ||
|
|
85995d51e4 | ||
|
|
2557a2fe5b | ||
|
|
4321573dfb | ||
|
|
70d4d6c56f | ||
|
|
e1e492832e | ||
|
|
1950394f7d | ||
|
|
308f7d93f5 | ||
|
|
de85bf848d | ||
|
|
7a6bf468bc | ||
|
|
9cb6346c4d | ||
|
|
f49eb66bb7 | ||
|
|
324ef9ca29 | ||
|
|
5b4dcc67b2 | ||
|
|
9010218b10 | ||
|
|
61144a10b3 | ||
|
|
7a1f737c65 | ||
|
|
546c032d43 | ||
|
|
b1869be3d9 | ||
|
|
574fdfecaa | ||
|
|
25c778965c | ||
|
|
976c0040b5 | ||
|
|
b53c62b40c | ||
|
|
2cdc56c882 | ||
|
|
f5059d8008 | ||
|
|
792ec4d731 | ||
|
|
93d08188ea | ||
|
|
e9ad5a6c66 | ||
|
|
8b0f118e2d | ||
|
|
60c89e28d3 | ||
|
|
38155051f5 | ||
|
|
7b215d5c6e | ||
|
|
1000e0756b | ||
|
|
902b497526 | ||
|
|
039f954e70 | ||
|
|
5dc2b127fa | ||
|
|
252cc44841 | ||
|
|
96d9ce4761 | ||
|
|
7f294d6632 | ||
|
|
e17de9065b | ||
|
|
098687de45 | ||
|
|
6da3342f22 | ||
|
|
47337540f5 | ||
|
|
7e588f01b0 | ||
|
|
c7c6c9dae4 | ||
|
|
c77cfc4dcd | ||
|
|
1bd61076ea | ||
|
|
bff4a6f707 | ||
|
|
1cc266c675 | ||
|
|
72099dfde5 | ||
|
|
c9c45baabc | ||
|
|
a01022c217 | ||
|
|
c3459e6820 | ||
|
|
e031e6d760 | ||
|
|
6c63b53cf4 | ||
|
|
1aa50699ad | ||
|
|
b2c54ae8c0 | ||
|
|
b16c9f4b30 | ||
|
|
a8d80eaddf | ||
|
|
9baf1ed73c | ||
|
|
ce3ea656ad | ||
|
|
ed49212f5a | ||
|
|
ad1242c47d | ||
|
|
890fcfb506 | ||
|
|
d7723615fe | ||
|
|
6517065a1a | ||
|
|
9223a7d403 | ||
|
|
7ce6703c5b | ||
|
|
37261cddbb | ||
|
|
d8c2ef6f3b | ||
|
|
98e5324409 | ||
|
|
a69c2a23f2 | ||
|
|
d8de093e4d | ||
|
|
678564dbb3 | ||
|
|
09d2601e85 | ||
|
|
41560682a1 | ||
|
|
473f8e4bb6 | ||
|
|
67446285c1 | ||
|
|
e12f4688d3 | ||
|
|
2581bbe203 | ||
|
|
995f2e0248 | ||
|
|
e2c8f79429 | ||
|
|
763c93857d | ||
|
|
55d2ef9c25 | ||
|
|
f37cbd1616 | ||
|
|
2a3449d0f2 | ||
|
|
596693c206 | ||
|
|
22aa07c4ba | ||
|
|
62001c1e29 | ||
|
|
ca85c94fe5 | ||
|
|
637e1e25a6 | ||
|
|
09696c1c4d | ||
|
|
298a8342b8 | ||
|
|
d64220426b | ||
|
|
88efdc361c | ||
|
|
cc1b371198 | ||
|
|
d9e4a3c1d4 | ||
|
|
0ae13acca0 | ||
|
|
d383fa31ae | ||
|
|
d1de394cd9 | ||
|
|
14c48bcc64 | ||
|
|
138edffb0a | ||
|
|
eb8f6ba17a | ||
|
|
beff05765b | ||
|
|
3b7ded3b96 | ||
|
|
edc86a10b3 | ||
|
|
39e9c55fae | ||
|
|
1b2c567787 | ||
|
|
d3955479f7 | ||
|
|
300a52f9fe | ||
|
|
2e3472d88f | ||
|
|
6b106fe365 | ||
|
|
bfd28c281e | ||
|
|
c307567025 | ||
|
|
5dd96f518b | ||
|
|
6d9010b11c | ||
|
|
9cc735d854 |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
ko_fi: papatutuwawa
|
||||||
@@ -10,3 +10,9 @@ build/
|
|||||||
# Omit committing pubspec.lock for library packages; see
|
# Omit committing pubspec.lock for library packages; see
|
||||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
|
||||||
|
# Omit pubspec override files generated by melos
|
||||||
|
**/pubspec_overrides.yaml
|
||||||
|
|
||||||
|
# Flake results
|
||||||
|
result
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[general]
|
||||||
|
ignore=B5,B6,B7,B8
|
||||||
|
|
||||||
|
[title-max-length]
|
||||||
|
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|all)+(,(meta|tests|style|docs|xep|core|example|all))*\)|release): [A-Z0-9].*$
|
||||||
|
|
||||||
|
|
||||||
|
[body-trailing-whitespace]
|
||||||
|
[body-first-line-empty]
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
pipeline:
|
||||||
|
# Check moxxmpp
|
||||||
|
moxxmpp-lint:
|
||||||
|
image: dart:2.18.1
|
||||||
|
commands:
|
||||||
|
- cd packages/moxxmpp
|
||||||
|
- dart pub get
|
||||||
|
- dart analyze --fatal-infos --fatal-warnings
|
||||||
|
moxxmpp-test:
|
||||||
|
image: dart:2.18.1
|
||||||
|
commands:
|
||||||
|
- cd packages/moxxmpp
|
||||||
|
- dart pub get
|
||||||
|
- dart test
|
||||||
|
|
||||||
|
# Check moxxmpp_socket_tcp
|
||||||
|
moxxmpp_socket_tcp-lint:
|
||||||
|
image: dart:2.18.1
|
||||||
|
commands:
|
||||||
|
- cd packages/moxxmpp_socket_tcp
|
||||||
|
- dart pub get
|
||||||
|
- dart analyze --fatal-infos --fatal-warnings
|
||||||
|
# moxxmpp-test:
|
||||||
|
# image: dart:2.18.1
|
||||||
|
# commands:
|
||||||
|
# - cd packages/moxxmpp
|
||||||
|
# - dart pub get
|
||||||
|
# - dart test
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Contribution Guide
|
||||||
|
|
||||||
|
Thanks for your interest in the moxxmpp XMPP library! This document contains guidelines and guides for working on the moxxmpp codebase.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you want to fix a small issue, you can just fork, create a new branch, and start working right away. However, if you want to work
|
||||||
|
on a bigger feature, please first create an issue (if an issue does not already exist) or join the [development chat](xmpp:moxxy@muc.moxxy.org?join) (xmpp:moxxy@muc.moxxy.org?join)
|
||||||
|
to discuss the feature first.
|
||||||
|
|
||||||
|
Before creating a pull request, please make sure you checked every item on the following checklist:
|
||||||
|
|
||||||
|
- [ ] I formatted the code with the dart formatter (`dart format`) before running the linter
|
||||||
|
- [ ] I ran the linter (`dart analyze`) and introduced no new linter warnings
|
||||||
|
- [ ] I ran the tests (`dart test`) and introduced no new failing tests
|
||||||
|
- [ ] I used [gitlint](https://github.com/jorisroovers/gitlint) to ensure propper formatting of my commig messages
|
||||||
|
|
||||||
|
If you think that your code is ready for a pull request, but you are not sure if it is ready, prefix the PR's title with "WIP: ", so that discussion
|
||||||
|
can happen there. If you think your PR is ready for review, remove the "WIP: " prefix.
|
||||||
@@ -3,13 +3,15 @@
|
|||||||
moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
|
moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
### moxxmpp
|
### [moxxmpp](./packages/moxxmpp)
|
||||||
|
|
||||||
This package contains the actual XMPP code that is platform-independent.
|
This package contains the actual XMPP code that is platform-independent.
|
||||||
|
|
||||||
### moxxmpp_socket
|
Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||||
|
|
||||||
`moxxmpp_socket` contains the implementation of the `BaseSocketWrapper` class that
|
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
|
||||||
|
|
||||||
|
`moxxmpp_socket_tcp` contains the implementation of the `BaseSocketWrapper` class that
|
||||||
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
|
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
|
||||||
if a DNS implementation is given, and supports StartTLS.
|
if a DNS implementation is given, and supports StartTLS.
|
||||||
|
|
||||||
@@ -22,6 +24,21 @@ 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
|
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
|
||||||
`ANDROID_AVD_HOME` are pointing to the correct directories.
|
`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
|
## License
|
||||||
|
|
||||||
See `./LICENSE`.
|
See `./LICENSE`.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you like what I do and you want to support me, feel free to donate to me on Ko-Fi.
|
||||||
|
|
||||||
|
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/assets/repo/kofi.png" height="36" style="height: 36px; border: 0px;"></img>](https://ko-fi.com/papatutuwawa)
|
||||||
@@ -11,5 +11,3 @@ analyzer:
|
|||||||
exclude:
|
exclude:
|
||||||
- "**/*.g.dart"
|
- "**/*.g.dart"
|
||||||
- "**/*.freezed.dart"
|
- "**/*.freezed.dart"
|
||||||
- "test/"
|
|
||||||
- "integration_test/"
|
|
||||||
@@ -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
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -5,25 +5,28 @@ import 'package:moxxmpp/moxxmpp.dart';
|
|||||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
class ExampleTcpSocketWrapper extends TCPSocketWrapper {
|
class ExampleTcpSocketWrapper extends TCPSocketWrapper {
|
||||||
ExampleTcpSocketWrapper() : super(false);
|
ExampleTcpSocketWrapper() : super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
||||||
final records = await MoxdnsPlugin.srvQuery(domain, false);
|
final records = await MoxdnsPlugin.srvQuery(domain, false);
|
||||||
return records
|
return records
|
||||||
.map((record) => MoxSrvRecord(
|
.map(
|
||||||
record.priority,
|
(record) => MoxSrvRecord(
|
||||||
record.weight,
|
record.priority,
|
||||||
record.target,
|
record.weight,
|
||||||
record.port,
|
record.target,
|
||||||
),)
|
record.port,
|
||||||
.toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Logger.root.level = Level.ALL;
|
Logger.root.level = Level.ALL;
|
||||||
Logger.root.onRecord.listen((record) {
|
Logger.root.onRecord.listen((record) {
|
||||||
|
// ignore: avoid_print
|
||||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -54,22 +57,32 @@ class MyHomePage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
|
final logger = Logger('MyHomePage');
|
||||||
final XmppConnection connection = XmppConnection(
|
final XmppConnection connection = XmppConnection(
|
||||||
ExponentialBackoffReconnectionPolicy(),
|
RandomBackoffReconnectionPolicy(1, 60),
|
||||||
ExampleTcpSocketWrapper(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
// The below causes the app to crash.
|
||||||
|
//ExampleTcpSocketWrapper(),
|
||||||
|
// In a production app, the below should be false.
|
||||||
|
TCPSocketWrapper(),
|
||||||
);
|
);
|
||||||
TextEditingController jidController = TextEditingController();
|
TextEditingController jidController = TextEditingController();
|
||||||
TextEditingController passwordController = TextEditingController();
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
bool connected = false;
|
||||||
|
bool loading = false;
|
||||||
|
|
||||||
_MyHomePageState() : super() {
|
_MyHomePageState() : super() {
|
||||||
connection
|
connection
|
||||||
..registerManagers([
|
..registerManagers([
|
||||||
StreamManagementManager(),
|
StreamManagementManager(),
|
||||||
DiscoManager(),
|
DiscoManager([]),
|
||||||
RosterManager(),
|
RosterManager(TestingRosterStateManager("", [])),
|
||||||
PingManager(),
|
PingManager(
|
||||||
|
const Duration(minutes: 3),
|
||||||
|
),
|
||||||
MessageManager(),
|
MessageManager(),
|
||||||
PresenceManager('http://moxxmpp.example'),
|
PresenceManager(),
|
||||||
])
|
])
|
||||||
..registerFeatureNegotiators([
|
..registerFeatureNegotiators([
|
||||||
ResourceBindingNegotiator(),
|
ResourceBindingNegotiator(),
|
||||||
@@ -78,21 +91,44 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
CSINegotiator(),
|
CSINegotiator(),
|
||||||
RosterFeatureNegotiator(),
|
RosterFeatureNegotiator(),
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
|
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||||
SaslScramNegotiator(9, '', '', ScramHashType.sha256),
|
SaslScramNegotiator(9, '', '', ScramHashType.sha256),
|
||||||
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
|
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _buttonPressed() async {
|
Future<void> _buttonPressed() async {
|
||||||
connection.setConnectionSettings(
|
if (connected) {
|
||||||
ConnectionSettings(
|
await connection.disconnect();
|
||||||
jid: JID.fromString(jidController.text),
|
setState(() {
|
||||||
password: passwordController.text,
|
connected = false;
|
||||||
useDirectTLS: true,
|
});
|
||||||
allowPlainAuth: false,
|
return;
|
||||||
),
|
}
|
||||||
|
setState(() {
|
||||||
|
loading = true;
|
||||||
|
});
|
||||||
|
connection.connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString(jidController.text),
|
||||||
|
password: passwordController.text,
|
||||||
);
|
);
|
||||||
await connection.connect();
|
final result = await connection.connect(waitUntilLogin: true);
|
||||||
|
setState(() {
|
||||||
|
connected = result.isType<bool>() && result.get<bool>();
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
|
if (result.isType<XmppError>()) {
|
||||||
|
logger.severe(result.get<XmppError>());
|
||||||
|
if (context.mounted) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => AlertDialog(
|
||||||
|
title: const Text('Error'),
|
||||||
|
content: Text(result.get<XmppError>().toString()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,20 +136,24 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
|
backgroundColor: connected ? Colors.green : Colors.deepPurple[800],
|
||||||
|
foregroundColor: connected ? Colors.black : Colors.white,
|
||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
TextField(
|
TextField(
|
||||||
|
enabled: !loading,
|
||||||
controller: jidController,
|
controller: jidController,
|
||||||
decoration: InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'JID',
|
labelText: 'JID',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextField(
|
TextField(
|
||||||
|
enabled: !loading,
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
decoration: InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
),
|
),
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
@@ -121,10 +161,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: _buttonPressed,
|
onPressed: _buttonPressed,
|
||||||
|
label: Text(connected ? 'Disconnect' : 'Connect'),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
tooltip: 'Connect',
|
tooltip: 'Connect',
|
||||||
child: const Icon(Icons.add),
|
icon: const Icon(Icons.power),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -16,10 +16,10 @@ dependencies:
|
|||||||
version: 0.1.4+1
|
version: 0.1.4+1
|
||||||
moxxmpp:
|
moxxmpp:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
version: 0.1.2+1
|
version: 0.3.1
|
||||||
moxxmpp_socket_tcp:
|
moxxmpp_socket_tcp:
|
||||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
version: 0.1.2+1
|
version: 0.3.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build output.
|
||||||
|
build/
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||||
|
|
||||||
|
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||||
|
@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.copyWith(done: true);
|
||||||
|
|
||||||
|
final bodyText = body.innerText();
|
||||||
|
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
Stanza.message(
|
||||||
|
to: stanza.from,
|
||||||
|
children: [
|
||||||
|
XMLNode(
|
||||||
|
tag: 'body',
|
||||||
|
text: 'Hello, ${stanza.from}! You said "$bodyText"',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return state.copyWith(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));
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
name: example_dart
|
||||||
|
description: A sample command-line application.
|
||||||
|
version: 1.0.0
|
||||||
|
# homepage: https://www.example.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.18.0 <3.0.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
logging: ^1.0.2
|
||||||
|
moxxmpp:
|
||||||
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
|
version: 0.3.1
|
||||||
|
moxxmpp_socket_tcp:
|
||||||
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
|
version: 0.3.1
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
moxxmpp:
|
||||||
|
path: ../packages/moxxmpp
|
||||||
|
moxxmpp_socket_tcp:
|
||||||
|
path: ../packages/moxxmpp_socket_tcp
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^2.0.0
|
||||||
|
test: ^1.16.0
|
||||||
Generated
+24
-7
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1667395993,
|
"lastModified": 1678901627,
|
||||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -17,11 +17,27 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1667610399,
|
"lastModified": 1676076353,
|
||||||
"narHash": "sha256-XZd0f4ZWAY0QOoUSdiNWj/eFiKb4B9CJPtl9uO9SYY4=",
|
"narHash": "sha256-mdUtE8Tp40cZETwcq5tCwwLqkJVV1ULJQ5GKRtbshag=",
|
||||||
|
"owner": "AtaraxiaSjel",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5deb99bdccbbb97e7562dee4ba8a3ee3021688e6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "AtaraxiaSjel",
|
||||||
|
"ref": "update/flutter",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-unstable": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1680273054,
|
||||||
|
"narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1dd8696f96db47156e1424a49578fe7dd4ce99a4",
|
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -34,7 +50,8 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"nixpkgs-unstable": "nixpkgs-unstable"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
description = "moxxmpp";
|
description = "moxxmpp";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:AtaraxiaSjel/nixpkgs/update/flutter";
|
||||||
|
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
config = {
|
config = {
|
||||||
@@ -13,6 +14,9 @@
|
|||||||
allowUnfree = true;
|
allowUnfree = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
unstable = import nixpkgs-unstable {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
android = pkgs.androidenv.composeAndroidPackages {
|
android = pkgs.androidenv.composeAndroidPackages {
|
||||||
# TODO: Find a way to pin these
|
# TODO: Find a way to pin these
|
||||||
#toolsVersion = "26.1.1";
|
#toolsVersion = "26.1.1";
|
||||||
@@ -29,15 +33,49 @@
|
|||||||
useGoogleAPIs = false;
|
useGoogleAPIs = false;
|
||||||
useGoogleTVAddOns = false;
|
useGoogleTVAddOns = false;
|
||||||
};
|
};
|
||||||
pinnedJDK = pkgs.jdk;
|
pinnedJDK = pkgs.jdk17;
|
||||||
|
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
pyyaml
|
||||||
|
requests
|
||||||
|
]);
|
||||||
|
|
||||||
|
moxxmppPubCache = import ./nix/pubcache.moxxmpp.nix {
|
||||||
|
inherit (pkgs) fetchzip runCommand;
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
devShell = pkgs.mkShell {
|
packages = {
|
||||||
|
moxxmppDartDocs = pkgs.callPackage ./nix/moxxmpp-docs.nix {
|
||||||
|
inherit (moxxmppPubCache) pubCache;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
devShell = let
|
||||||
|
prosody-newer-community-modules = unstable.prosody.overrideAttrs (old: {
|
||||||
|
communityModules = pkgs.fetchhg {
|
||||||
|
url = "https://hg.prosody.im/prosody-modules";
|
||||||
|
rev = "e3a3a6c86a9f";
|
||||||
|
sha256 = "sha256-C2x6PCv0sYuj4/SroDOJLsNPzfeNCodYKbMqmNodFrk=";
|
||||||
|
};
|
||||||
|
|
||||||
|
src = pkgs.fetchhg {
|
||||||
|
url = "https://hg.prosody.im/trunk";
|
||||||
|
rev = "8a2f75e38eb2";
|
||||||
|
sha256 = "sha256-zMNp9+wQ/hvUVyxFl76DqCVzQUPP8GkNdstiTDkG8Hw=";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
prosody-sasl2 = prosody-newer-community-modules.override {
|
||||||
|
withCommunityModules = [
|
||||||
|
"sasl2" "sasl2_fast" "sasl2_sm" "sasl2_bind2"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
flutter pinnedJDK android.platform-tools dart # Flutter/Android
|
flutter pinnedJDK android.platform-tools dart # Dart
|
||||||
gitlint # Code hygiene
|
gitlint # Code hygiene
|
||||||
ripgrep # General utilities
|
ripgrep # General utilities
|
||||||
|
|
||||||
# Flutter dependencies for linux desktop
|
# Flutter dependencies for Linux desktop
|
||||||
atk
|
atk
|
||||||
cairo
|
cairo
|
||||||
clang
|
clang
|
||||||
@@ -53,6 +91,13 @@
|
|||||||
pkg-config
|
pkg-config
|
||||||
xorg.libX11
|
xorg.libX11
|
||||||
xorg.xorgproto
|
xorg.xorgproto
|
||||||
|
|
||||||
|
# For the scripts in ./scripts/
|
||||||
|
pythonEnv
|
||||||
|
|
||||||
|
# For integration testing against a local prosody server
|
||||||
|
prosody-sasl2
|
||||||
|
mkcert
|
||||||
];
|
];
|
||||||
|
|
||||||
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build output.
|
||||||
|
build/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Integration Tests
|
||||||
|
|
||||||
|
The included `./prosody.cfg.lua` config file must be used for integration testing.
|
||||||
|
Additionally, ensure that a user `testuser@localhost` with the password `abc123`
|
||||||
|
exists. Note that this currently requires prosody-trunk.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
include: ../analysis_options.yaml
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEAzCCAmugAwIBAgIQd61NPnP8++X7h8a+85C6DjANBgkqhkiG9w0BAQsFADBZ
|
||||||
|
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFzAVBgNVBAsMDmFsZXhh
|
||||||
|
bmRlckBtaWt1MR4wHAYDVQQDDBVta2NlcnQgYWxleGFuZGVyQG1pa3UwHhcNMjMw
|
||||||
|
NDAyMTM1ODIxWhcNMjUwNzAyMTM1ODIxWjBCMScwJQYDVQQKEx5ta2NlcnQgZGV2
|
||||||
|
ZWxvcG1lbnQgY2VydGlmaWNhdGUxFzAVBgNVBAsMDmFsZXhhbmRlckBtaWt1MIIB
|
||||||
|
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1DElEXPY+VDQP7cSikK0ne0K
|
||||||
|
gDgorGYPG9R7lOeuPLHyFYYry78+hB037OT0BOyA2uTu1yrog0dI/4YGicPDIqXh
|
||||||
|
IgHfjV+4kMi5SgO7ECWOBmZFqTC3bBwvbNtoW40aFjYSFaOkm/nnfp+nalEJJZ/N
|
||||||
|
kSkD4gdT3pH1ClsovlI4BlsxeIoJtyGzxMidJVXDAqMNraLatzJBwnT3OEs93xTf
|
||||||
|
7Kd1KUpQp9OZFrGi15zv/n6tCmrcC3xMOVHuYkhW0UCTFmev7ZqbghQsQ9N9s0E6
|
||||||
|
kk9rUf9xtMNH4Af6+2YRkT1DAGQ6FkXl1nQdB5H5XRgOBl+3k9s8wUrxQvQddQID
|
||||||
|
AQABo14wXDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
|
||||||
|
VR0jBBgwFoAU54aUZ+dytAOBTsYIdGtSnjiig/gwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||||
|
b3N0MA0GCSqGSIb3DQEBCwUAA4IBgQBU8p7Ua0Cs+lXlWmtCh2j+YF9R+dvc+3Iw
|
||||||
|
dYEzCmYd375uxPctyHXW0yYjyuH9WuYn0F7OicEFEeC2+exHND+/z0J2Zv5yu34r
|
||||||
|
SfgHVfvE/Vxisn9InYrUCVtfRwLDF3HgLyIlm8FVzIyiIANhpe6vJdqjEWTsiL2X
|
||||||
|
I6hoDf1xlRgEqUx+Wxl2IFWrg+1SPPGTQzDPImiRlz8d+9ZJ9v48vaV5+aITMvDP
|
||||||
|
Gfm/bnNXXd5Gf7nGwL8zFHiwLoYQ5AUYl0IfXYwFAXJ72+LjiRT33IOidVJF0gsQ
|
||||||
|
6k9cTsc4lIrt4FOzdchalbF1Eu2prieWoZxz0apG8OuUeAhaB+t8kT6swAkwvkLW
|
||||||
|
OnlSATm9Cls9Pc4XDHTbZlbMmwF2Jmukgz/l1vlTutt4ZgZwQkSEa9Qfoi9Zym0R
|
||||||
|
iKls1CgD49zguR/cFDKK3agvfv6Afw6HdgaS/WqcI/Ros7b+RCkbAlAG5gqr6BLQ
|
||||||
|
8RGyVjZSC4Mz/ddcnMEpRAnjuFJjhGA=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUMSURc9j5UNA/
|
||||||
|
txKKQrSd7QqAOCisZg8b1HuU5648sfIVhivLvz6EHTfs5PQE7IDa5O7XKuiDR0j/
|
||||||
|
hgaJw8MipeEiAd+NX7iQyLlKA7sQJY4GZkWpMLdsHC9s22hbjRoWNhIVo6Sb+ed+
|
||||||
|
n6dqUQkln82RKQPiB1PekfUKWyi+UjgGWzF4igm3IbPEyJ0lVcMCow2totq3MkHC
|
||||||
|
dPc4Sz3fFN/sp3UpSlCn05kWsaLXnO/+fq0KatwLfEw5Ue5iSFbRQJMWZ6/tmpuC
|
||||||
|
FCxD032zQTqST2tR/3G0w0fgB/r7ZhGRPUMAZDoWReXWdB0HkfldGA4GX7eT2zzB
|
||||||
|
SvFC9B11AgMBAAECggEAYaj4yY6LFzxVjG2i79WBsYnOonK2bZpPa9ygwEjdTXwM
|
||||||
|
0lE9SPoNONsFyVca5EVBjP1+27MY7orZkxlJWxCpeAHmmzNHg5bBqIlpliIfb3AJ
|
||||||
|
bPKXLyaH1Q8n2K8m2bQYhI6ARktZ0Jv1KrcqY2lGj3V8NEovSlFbDX4ZzJlmKCly
|
||||||
|
d4Ia6eQ7f9AjgsOwpQGeCTF7WLaVDnch6D4JfCGrW08lFeaqogiBQczsOE3hcNSd
|
||||||
|
tEul21Z0CkC7Iiw28KdkApPINquo1VYdAcOvUCOXkwJfPC1gsJwK4O2jxfi9v5NF
|
||||||
|
uU1niK0/00b396pQKvXpkfViynexwzK0MZCoo3zuQQKBgQDzaZexcniQNDyWqN3C
|
||||||
|
oMe4V3rnxs+aO/lu8Ed3mng+Jf4vuarZlxNot7WRBMGT/T+b7/UIrqRJy50CYAPY
|
||||||
|
3RRR84tLg3UMwUWhDYsPucNc2icODBG4c+QWJ300W19r+J+iT8PwS9AbH2n094Rn
|
||||||
|
LCRYFrX5aMsgIH5uwuncKzweMQKBgQDfKj2i1ptC53aOcr1tMCFYcnMGtaAZ8u6+
|
||||||
|
cKSgnzKlTw/g0EYlGcETUnCyZe0oVYWp3y859FBXU0JMDmxu84aYEZNF6BwRVlpF
|
||||||
|
feQgtUFZHyf9MepQGhjIJ5El8n7jhh1bsBY18QbDFe6/GtqPx/mQEF7vE+wPFl9h
|
||||||
|
putwdv3OhQKBgGKPyi2/BVSW4kW7IPiTM+vP+GNrnFp+mHS0dKvYb4HyzmcyzhyH
|
||||||
|
UQOhB7Mt8thivmP9GQIn/TwoZ24zxLsGYhkA/dFY7Id6pyAcpMd8V7/8Ub4dYvuG
|
||||||
|
acASw1709MF6jeEiXVuqxxyEbtoTc5h3Rkwo/gx8w2tB3RAqepl9JD2xAoGAfVL3
|
||||||
|
ci8a2iOqTKza/Cp/T3BWcHonAuuOb5xKl3lPs84GmLXd7o/cAcHWUBk1aeU9Pvx7
|
||||||
|
RQyS4bd8D8I52sUf3N5h2mxS9tmLsGLWbhfcLvR0PJh/gaRmLmEp/imEYLm8WvU0
|
||||||
|
Q+6rYXs7rE6kVwJygBjxd0m003Q49FoM9gec2RECgYEA5SLAe2UmJSLIb0DKk27o
|
||||||
|
nSfARDSdi9N40vIjDFHmDRdKTOYicED/f7KqXnxVpvFxDdCvJ7xeC4V7vkaqiiwd
|
||||||
|
/oMLQq0GjmBxG/PNd1AFIWDydyH+JcY6U4XWIzIw92OKVYC/KMvd2f9orTfmDyAU
|
||||||
|
RsGMfgV90kCzouAZKy3yPmo=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
admins = { }
|
||||||
|
plugin_paths = {}
|
||||||
|
|
||||||
|
modules_enabled = {
|
||||||
|
-- Generally required
|
||||||
|
"disco"; -- Service discovery
|
||||||
|
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||||
|
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||||
|
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||||
|
|
||||||
|
-- Not essential, but recommended
|
||||||
|
"blocklist"; -- Allow users to block communications with other users
|
||||||
|
"bookmarks"; -- Synchronise the list of open rooms between clients
|
||||||
|
"carbons"; -- Keep multiple online clients in sync
|
||||||
|
"dialback"; -- Support for verifying remote servers using DNS
|
||||||
|
"limits"; -- Enable bandwidth limiting for XMPP connections
|
||||||
|
"pep"; -- Allow users to store public and private data in their account
|
||||||
|
"private"; -- Legacy account storage mechanism (XEP-0049)
|
||||||
|
"smacks"; -- Stream management and resumption (XEP-0198)
|
||||||
|
"vcard4"; -- User profiles (stored in PEP)
|
||||||
|
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
|
||||||
|
|
||||||
|
-- Nice to have
|
||||||
|
"csi_simple"; -- Simple but effective traffic optimizations for mobile devices
|
||||||
|
"invites"; -- Create and manage invites
|
||||||
|
"invites_adhoc"; -- Allow admins/users to create invitations via their client
|
||||||
|
"invites_register"; -- Allows invited users to create accounts
|
||||||
|
"ping"; -- Replies to XMPP pings with pongs
|
||||||
|
"register"; -- Allow users to register on this server using a client and change passwords
|
||||||
|
"time"; -- Let others know the time here on this server
|
||||||
|
"uptime"; -- Report how long server has been running
|
||||||
|
"version"; -- Replies to server version requests
|
||||||
|
|
||||||
|
-- SASL2
|
||||||
|
"sasl2";
|
||||||
|
"sasl2_sm";
|
||||||
|
"sasl2_fast";
|
||||||
|
"sasl2_bind2";
|
||||||
|
}
|
||||||
|
|
||||||
|
s2s_secure_auth = false
|
||||||
|
|
||||||
|
-- Authentication
|
||||||
|
authentication = "internal_plain"
|
||||||
|
|
||||||
|
-- Storage
|
||||||
|
storage = "internal"
|
||||||
|
data_path = "/tmp/prosody-data/"
|
||||||
|
log = {
|
||||||
|
debug = "*console";
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
name: integration_tests
|
||||||
|
description: A sample command-line application.
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.18.0 <3.0.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
logging: ^1.0.2
|
||||||
|
moxxmpp: 0.3.0
|
||||||
|
moxxmpp_socket_tcp: 0.3.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^2.0.0
|
||||||
|
test: ^1.16.0
|
||||||
|
very_good_analysis: ^3.0.1
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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 {
|
||||||
|
@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);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
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 {
|
||||||
|
@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 authenticating against Prosody with SASL2, Bind2, and FAST',
|
||||||
|
() async {
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
TestingTCPSocketWrapper(),
|
||||||
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('testuser@localhost'),
|
||||||
|
password: 'abc123',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 5222,
|
||||||
|
);
|
||||||
|
final csi = CSIManager();
|
||||||
|
await csi.setInactive(sendNonza: false);
|
||||||
|
await conn.registerManagers([
|
||||||
|
RosterManager(TestingRosterStateManager('', [])),
|
||||||
|
DiscoManager([]),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
FASTSaslNegotiator(),
|
||||||
|
Bind2Negotiator(),
|
||||||
|
StartTlsNegotiator(),
|
||||||
|
Sasl2Negotiator(
|
||||||
|
userAgent: const UserAgent(
|
||||||
|
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||||
|
software: 'moxxmpp',
|
||||||
|
device: "PapaTutuWawa's awesome device",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final result = await conn.connect(
|
||||||
|
waitUntilLogin: true,
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
);
|
||||||
|
expect(result.isType<bool>(), true);
|
||||||
|
expect(
|
||||||
|
conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state,
|
||||||
|
NegotiatorState.done,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
conn
|
||||||
|
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
|
||||||
|
.fastToken !=
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
stdenv
|
||||||
|
, pubCache
|
||||||
|
, dart
|
||||||
|
, lib
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "moxxmpp-docs";
|
||||||
|
version = "0.3.1";
|
||||||
|
|
||||||
|
PUB_CACHE = "${pubCache}";
|
||||||
|
|
||||||
|
src = "${./..}/packages/moxxmpp";
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
(
|
||||||
|
set -x
|
||||||
|
echo $PUB_CACHE
|
||||||
|
${dart}/bin/dart pub get --no-precompile --offline
|
||||||
|
)
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
${dart}/bin/dart doc -o $out
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,730 @@
|
|||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5
|
||||||
|
source: hosted
|
||||||
|
version: 50.0.0
|
||||||
|
analyzer:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r
|
||||||
|
source: hosted
|
||||||
|
version: 5.2.0
|
||||||
|
args:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g
|
||||||
|
source: hosted
|
||||||
|
version: 2.3.1
|
||||||
|
async:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx
|
||||||
|
source: hosted
|
||||||
|
version: 2.10.0
|
||||||
|
boolean_selector:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.1
|
||||||
|
build:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd
|
||||||
|
source: hosted
|
||||||
|
version: 2.3.1
|
||||||
|
build_config:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj
|
||||||
|
source: hosted
|
||||||
|
version: 1.1.1
|
||||||
|
build_daemon:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j
|
||||||
|
source: hosted
|
||||||
|
version: 3.1.0
|
||||||
|
build_resolvers:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.0
|
||||||
|
build_runner:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz
|
||||||
|
dependency: direct dev
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6
|
||||||
|
source: hosted
|
||||||
|
version: 2.3.2
|
||||||
|
build_runner_core:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z
|
||||||
|
source: hosted
|
||||||
|
version: 7.2.7
|
||||||
|
built_collection:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3
|
||||||
|
source: hosted
|
||||||
|
version: 5.1.1
|
||||||
|
built_value:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i
|
||||||
|
source: hosted
|
||||||
|
version: 8.4.2
|
||||||
|
checked_yaml:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k
|
||||||
|
source: hosted
|
||||||
|
version: 2.0.1
|
||||||
|
code_builder:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg
|
||||||
|
source: hosted
|
||||||
|
version: 4.3.0
|
||||||
|
collection:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545
|
||||||
|
source: hosted
|
||||||
|
version: 1.17.0
|
||||||
|
convert:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri
|
||||||
|
source: hosted
|
||||||
|
version: 3.1.1
|
||||||
|
coverage:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d
|
||||||
|
source: hosted
|
||||||
|
version: 1.6.1
|
||||||
|
crypto:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds
|
||||||
|
source: hosted
|
||||||
|
version: 3.0.2
|
||||||
|
cryptography:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: cryptography
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs
|
||||||
|
source: hosted
|
||||||
|
version: 2.0.5
|
||||||
|
dart_style:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v
|
||||||
|
source: hosted
|
||||||
|
version: 2.2.4
|
||||||
|
file:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7
|
||||||
|
source: hosted
|
||||||
|
version: 6.1.4
|
||||||
|
fixnum:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/fixnum/versions/1.0.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.1
|
||||||
|
freezed:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/freezed/versions/2.1.1.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: freezed
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1i9s4djf4vlz56zqn8brcck3n7sk07qay23wmaan991cqydd10iq
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.1
|
||||||
|
freezed_annotation:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/freezed_annotation/versions/2.1.0.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0ym120dh1lpfnb68gxh1finm8p9l445q5x10aw8269y469b9k9z3
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.0
|
||||||
|
frontend_server_client:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj
|
||||||
|
source: hosted
|
||||||
|
version: 3.1.0
|
||||||
|
glob:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.0
|
||||||
|
graphs:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9
|
||||||
|
source: hosted
|
||||||
|
version: 2.2.0
|
||||||
|
hex:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: hex
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh
|
||||||
|
source: hosted
|
||||||
|
version: 0.2.0
|
||||||
|
http_multi_server:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql
|
||||||
|
source: hosted
|
||||||
|
version: 3.2.1
|
||||||
|
http_parser:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd
|
||||||
|
source: hosted
|
||||||
|
version: 4.0.2
|
||||||
|
io:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.3
|
||||||
|
js:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50
|
||||||
|
source: hosted
|
||||||
|
version: 0.6.5
|
||||||
|
json_annotation:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby
|
||||||
|
source: hosted
|
||||||
|
version: 4.7.0
|
||||||
|
json_serializable:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg
|
||||||
|
source: hosted
|
||||||
|
version: 6.5.4
|
||||||
|
logging:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.2
|
||||||
|
matcher:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0
|
||||||
|
source: hosted
|
||||||
|
version: 0.12.13
|
||||||
|
meta:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5
|
||||||
|
source: hosted
|
||||||
|
version: 1.8.0
|
||||||
|
mime:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.2
|
||||||
|
moxlib:
|
||||||
|
archive_url: https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: moxlib
|
||||||
|
url: https://git.polynom.me/api/packages/Moxxy/pub/
|
||||||
|
sha256: 1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq
|
||||||
|
source: hosted
|
||||||
|
version: 0.1.5
|
||||||
|
node_preamble:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx
|
||||||
|
source: hosted
|
||||||
|
version: 2.0.1
|
||||||
|
omemo_dart:
|
||||||
|
archive_url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: omemo_dart
|
||||||
|
url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/
|
||||||
|
sha256: 09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi
|
||||||
|
source: hosted
|
||||||
|
version: 0.4.3
|
||||||
|
package_config:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.0
|
||||||
|
path:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181
|
||||||
|
source: hosted
|
||||||
|
version: 1.8.2
|
||||||
|
pedantic:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pedantic
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad
|
||||||
|
source: hosted
|
||||||
|
version: 1.11.1
|
||||||
|
petitparser:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0
|
||||||
|
source: hosted
|
||||||
|
version: 5.1.0
|
||||||
|
pinenacl:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pinenacl
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg
|
||||||
|
source: hosted
|
||||||
|
version: 0.5.1
|
||||||
|
pool:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/pool/versions/1.5.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4
|
||||||
|
source: hosted
|
||||||
|
version: 1.5.1
|
||||||
|
pub_semver:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.2
|
||||||
|
pubspec_parse:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1
|
||||||
|
source: hosted
|
||||||
|
version: 1.2.1
|
||||||
|
random_string:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: random_string
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff
|
||||||
|
source: hosted
|
||||||
|
version: 2.3.1
|
||||||
|
saslprep:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: saslprep
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.2
|
||||||
|
shelf:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1
|
||||||
|
source: hosted
|
||||||
|
version: 1.4.0
|
||||||
|
shelf_packages_handler:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q
|
||||||
|
source: hosted
|
||||||
|
version: 3.0.1
|
||||||
|
shelf_static:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34
|
||||||
|
source: hosted
|
||||||
|
version: 1.1.1
|
||||||
|
shelf_web_socket:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.3
|
||||||
|
source_gen:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz
|
||||||
|
source: hosted
|
||||||
|
version: 1.2.6
|
||||||
|
source_helper:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd
|
||||||
|
source: hosted
|
||||||
|
version: 1.3.3
|
||||||
|
source_map_stack_trace:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.1
|
||||||
|
source_maps:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6
|
||||||
|
source: hosted
|
||||||
|
version: 0.10.11
|
||||||
|
source_span:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86
|
||||||
|
source: hosted
|
||||||
|
version: 1.9.1
|
||||||
|
stack_trace:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na
|
||||||
|
source: hosted
|
||||||
|
version: 1.11.0
|
||||||
|
stream_channel:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.1
|
||||||
|
stream_transform:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp
|
||||||
|
source: hosted
|
||||||
|
version: 2.1.0
|
||||||
|
string_scanner:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00
|
||||||
|
source: hosted
|
||||||
|
version: 1.2.0
|
||||||
|
synchronized:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i
|
||||||
|
source: hosted
|
||||||
|
version: 3.0.0+2
|
||||||
|
term_glyph:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9
|
||||||
|
source: hosted
|
||||||
|
version: 1.2.1
|
||||||
|
test:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz
|
||||||
|
dependency: direct dev
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x
|
||||||
|
source: hosted
|
||||||
|
version: 1.22.0
|
||||||
|
test_api:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px
|
||||||
|
source: hosted
|
||||||
|
version: 0.4.16
|
||||||
|
test_core:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005
|
||||||
|
source: hosted
|
||||||
|
version: 0.4.20
|
||||||
|
timing:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.0
|
||||||
|
typed_data:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g
|
||||||
|
source: hosted
|
||||||
|
version: 1.3.1
|
||||||
|
unorm_dart:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: unorm_dart
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v
|
||||||
|
source: hosted
|
||||||
|
version: 0.2.0
|
||||||
|
uuid:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg
|
||||||
|
source: hosted
|
||||||
|
version: 3.0.5
|
||||||
|
very_good_analysis:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz
|
||||||
|
dependency: direct dev
|
||||||
|
description:
|
||||||
|
name: very_good_analysis
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4
|
||||||
|
source: hosted
|
||||||
|
version: 3.1.0
|
||||||
|
vm_service:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5
|
||||||
|
source: hosted
|
||||||
|
version: 9.4.0
|
||||||
|
watcher:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg
|
||||||
|
source: hosted
|
||||||
|
version: 1.0.2
|
||||||
|
web_socket_channel:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a
|
||||||
|
source: hosted
|
||||||
|
version: 2.2.0
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx
|
||||||
|
source: hosted
|
||||||
|
version: 1.2.0
|
||||||
|
xml:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz
|
||||||
|
dependency: direct main
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01
|
||||||
|
source: hosted
|
||||||
|
version: 6.2.0
|
||||||
|
yaml:
|
||||||
|
archive_url: https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: https://pub.dartlang.org
|
||||||
|
sha256: 0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw
|
||||||
|
source: hosted
|
||||||
|
version: 3.1.1
|
||||||
@@ -0,0 +1,814 @@
|
|||||||
|
{fetchzip, runCommand} : rec {
|
||||||
|
_fe_analyzer_shared = fetchzip {
|
||||||
|
sha256 = "1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5";
|
||||||
|
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
args = fetchzip {
|
||||||
|
sha256 = "0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g";
|
||||||
|
url = "https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
async = fetchzip {
|
||||||
|
sha256 = "00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx";
|
||||||
|
url = "https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean_selector = fetchzip {
|
||||||
|
sha256 = "0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj";
|
||||||
|
url = "https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
build = fetchzip {
|
||||||
|
sha256 = "1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd";
|
||||||
|
url = "https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
build_config = fetchzip {
|
||||||
|
sha256 = "092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj";
|
||||||
|
url = "https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
build_daemon = fetchzip {
|
||||||
|
sha256 = "0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j";
|
||||||
|
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
build_runner = fetchzip {
|
||||||
|
sha256 = "0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6";
|
||||||
|
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.2.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
built_collection = fetchzip {
|
||||||
|
sha256 = "0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3";
|
||||||
|
url = "https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
built_value = fetchzip {
|
||||||
|
sha256 = "0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i";
|
||||||
|
url = "https://pub.dartlang.org/packages/built_value/versions/8.4.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
code_builder = fetchzip {
|
||||||
|
sha256 = "1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg";
|
||||||
|
url = "https://pub.dartlang.org/packages/code_builder/versions/4.3.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
convert = fetchzip {
|
||||||
|
sha256 = "0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri";
|
||||||
|
url = "https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
coverage = fetchzip {
|
||||||
|
sha256 = "0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d";
|
||||||
|
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
crypto = fetchzip {
|
||||||
|
sha256 = "1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds";
|
||||||
|
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
cryptography = fetchzip {
|
||||||
|
sha256 = "0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs";
|
||||||
|
url = "https://pub.dartlang.org/packages/cryptography/versions/2.0.5.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
file = fetchzip {
|
||||||
|
sha256 = "0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7";
|
||||||
|
url = "https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
glob = fetchzip {
|
||||||
|
sha256 = "0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs";
|
||||||
|
url = "https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
graphs = fetchzip {
|
||||||
|
sha256 = "0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9";
|
||||||
|
url = "https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
hex = fetchzip {
|
||||||
|
sha256 = "19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh";
|
||||||
|
url = "https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
http_multi_server = fetchzip {
|
||||||
|
sha256 = "1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql";
|
||||||
|
url = "https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
http_parser = fetchzip {
|
||||||
|
sha256 = "027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd";
|
||||||
|
url = "https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
io = fetchzip {
|
||||||
|
sha256 = "1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni";
|
||||||
|
url = "https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
js = fetchzip {
|
||||||
|
sha256 = "13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50";
|
||||||
|
url = "https://pub.dartlang.org/packages/js/versions/0.6.5.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
json_serializable = fetchzip {
|
||||||
|
sha256 = "04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg";
|
||||||
|
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
logging = fetchzip {
|
||||||
|
sha256 = "0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs";
|
||||||
|
url = "https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
matcher = fetchzip {
|
||||||
|
sha256 = "0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0";
|
||||||
|
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = fetchzip {
|
||||||
|
sha256 = "01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5";
|
||||||
|
url = "https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
mime = fetchzip {
|
||||||
|
sha256 = "1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896";
|
||||||
|
url = "https://pub.dartlang.org/packages/mime/versions/1.0.2.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
node_preamble = fetchzip {
|
||||||
|
sha256 = "0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx";
|
||||||
|
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
package_config = fetchzip {
|
||||||
|
sha256 = "1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc";
|
||||||
|
url = "https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = fetchzip {
|
||||||
|
sha256 = "16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181";
|
||||||
|
url = "https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
pedantic = fetchzip {
|
||||||
|
sha256 = "10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad";
|
||||||
|
url = "https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
petitparser = fetchzip {
|
||||||
|
sha256 = "1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0";
|
||||||
|
url = "https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
pinenacl = fetchzip {
|
||||||
|
sha256 = "0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg";
|
||||||
|
url = "https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
pool = fetchzip {
|
||||||
|
sha256 = "0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4";
|
||||||
|
url = "https://pub.dartlang.org/packages/pool/versions/1.5.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
pubspec_parse = fetchzip {
|
||||||
|
sha256 = "19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1";
|
||||||
|
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
random_string = fetchzip {
|
||||||
|
sha256 = "11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff";
|
||||||
|
url = "https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
saslprep = fetchzip {
|
||||||
|
sha256 = "04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7";
|
||||||
|
url = "https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
shelf = fetchzip {
|
||||||
|
sha256 = "0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1";
|
||||||
|
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.0.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
shelf_static = fetchzip {
|
||||||
|
sha256 = "1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34";
|
||||||
|
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
source_gen = fetchzip {
|
||||||
|
sha256 = "1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz";
|
||||||
|
url = "https://pub.dartlang.org/packages/source_gen/versions/1.2.6.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
source_map_stack_trace = fetchzip {
|
||||||
|
sha256 = "0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k";
|
||||||
|
url = "https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
source_maps = fetchzip {
|
||||||
|
sha256 = "18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6";
|
||||||
|
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.11.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
stack_trace = fetchzip {
|
||||||
|
sha256 = "0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na";
|
||||||
|
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
stream_transform = fetchzip {
|
||||||
|
sha256 = "0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp";
|
||||||
|
url = "https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
string_scanner = fetchzip {
|
||||||
|
sha256 = "0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00";
|
||||||
|
url = "https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
synchronized = fetchzip {
|
||||||
|
sha256 = "1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i";
|
||||||
|
url = "https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
term_glyph = fetchzip {
|
||||||
|
sha256 = "1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9";
|
||||||
|
url = "https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
test = fetchzip {
|
||||||
|
sha256 = "08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x";
|
||||||
|
url = "https://pub.dartlang.org/packages/test/versions/1.22.0.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
test_core = fetchzip {
|
||||||
|
sha256 = "1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005";
|
||||||
|
url = "https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
timing = fetchzip {
|
||||||
|
sha256 = "0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg";
|
||||||
|
url = "https://pub.dartlang.org/packages/timing/versions/1.0.0.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";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
unorm_dart = fetchzip {
|
||||||
|
sha256 = "05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v";
|
||||||
|
url = "https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
uuid = fetchzip {
|
||||||
|
sha256 = "12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg";
|
||||||
|
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
very_good_analysis = fetchzip {
|
||||||
|
sha256 = "1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4";
|
||||||
|
url = "https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
vm_service = fetchzip {
|
||||||
|
sha256 = "05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5";
|
||||||
|
url = "https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
watcher = fetchzip {
|
||||||
|
sha256 = "1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg";
|
||||||
|
url = "https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
web_socket_channel = fetchzip {
|
||||||
|
sha256 = "147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a";
|
||||||
|
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
webkit_inspection_protocol = fetchzip {
|
||||||
|
sha256 = "0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx";
|
||||||
|
url = "https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz";
|
||||||
|
stripRoot = false;
|
||||||
|
extension = "tar.gz";
|
||||||
|
};
|
||||||
|
|
||||||
|
xml = fetchzip {
|
||||||
|
sha256 = "0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01";
|
||||||
|
url = "https://pub.dartlang.org/packages/xml/versions/6.2.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";
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${analyzer} $out/hosted/pub.dartlang.org/analyzer-5.2.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.dartlang.org
|
||||||
|
ln -s ${async} $out/hosted/pub.dartlang.org/async-2.10.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.dartlang.org
|
||||||
|
ln -s ${build} $out/hosted/pub.dartlang.org/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.dartlang.org
|
||||||
|
ln -s ${build_daemon} $out/hosted/pub.dartlang.org/build_daemon-3.1.0
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${build_runner} $out/hosted/pub.dartlang.org/build_runner-2.3.2
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${built_collection} $out/hosted/pub.dartlang.org/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.dartlang.org
|
||||||
|
ln -s ${checked_yaml} $out/hosted/pub.dartlang.org/checked_yaml-2.0.1
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${collection} $out/hosted/pub.dartlang.org/collection-1.17.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.dartlang.org
|
||||||
|
ln -s ${coverage} $out/hosted/pub.dartlang.org/coverage-1.6.1
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${crypto} $out/hosted/pub.dartlang.org/crypto-3.0.2
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${cryptography} $out/hosted/pub.dartlang.org/cryptography-2.0.5
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${file} $out/hosted/pub.dartlang.org/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.dartlang.org
|
||||||
|
ln -s ${freezed} $out/hosted/pub.dartlang.org/freezed-2.1.1
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${frontend_server_client} $out/hosted/pub.dartlang.org/frontend_server_client-3.1.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${glob} $out/hosted/pub.dartlang.org/glob-2.1.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.dartlang.org
|
||||||
|
ln -s ${hex} $out/hosted/pub.dartlang.org/hex-0.2.0
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${http_parser} $out/hosted/pub.dartlang.org/http_parser-4.0.2
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${io} $out/hosted/pub.dartlang.org/io-1.0.3
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${js} $out/hosted/pub.dartlang.org/js-0.6.5
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${json_serializable} $out/hosted/pub.dartlang.org/json_serializable-6.5.4
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${logging} $out/hosted/pub.dartlang.org/logging-1.0.2
|
||||||
|
|
||||||
|
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/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
|
||||||
|
|
||||||
|
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/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
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${path} $out/hosted/pub.dartlang.org/path-1.8.2
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${pedantic} $out/hosted/pub.dartlang.org/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.dartlang.org
|
||||||
|
ln -s ${pinenacl} $out/hosted/pub.dartlang.org/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.dartlang.org
|
||||||
|
ln -s ${pub_semver} $out/hosted/pub.dartlang.org/pub_semver-2.1.2
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${random_string} $out/hosted/pub.dartlang.org/random_string-2.3.1
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${saslprep} $out/hosted/pub.dartlang.org/saslprep-1.0.2
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${shelf} $out/hosted/pub.dartlang.org/shelf-1.4.0
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${shelf_static} $out/hosted/pub.dartlang.org/shelf_static-1.1.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.dartlang.org
|
||||||
|
ln -s ${source_gen} $out/hosted/pub.dartlang.org/source_gen-1.2.6
|
||||||
|
|
||||||
|
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.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.dartlang.org
|
||||||
|
ln -s ${source_maps} $out/hosted/pub.dartlang.org/source_maps-0.10.11
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${stack_trace} $out/hosted/pub.dartlang.org/stack_trace-1.11.0
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${stream_transform} $out/hosted/pub.dartlang.org/stream_transform-2.1.0
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${synchronized} $out/hosted/pub.dartlang.org/synchronized-3.0.0+2
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${test} $out/hosted/pub.dartlang.org/test-1.22.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.dartlang.org
|
||||||
|
ln -s ${test_core} $out/hosted/pub.dartlang.org/test_core-0.4.20
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${timing} $out/hosted/pub.dartlang.org/timing-1.0.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.dartlang.org
|
||||||
|
ln -s ${unorm_dart} $out/hosted/pub.dartlang.org/unorm_dart-0.2.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${uuid} $out/hosted/pub.dartlang.org/uuid-3.0.5
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${vm_service} $out/hosted/pub.dartlang.org/vm_service-9.4.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${watcher} $out/hosted/pub.dartlang.org/watcher-1.0.2
|
||||||
|
|
||||||
|
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.dartlang.org
|
||||||
|
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dartlang.org/webkit_inspection_protocol-1.2.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${xml} $out/hosted/pub.dartlang.org/xml-6.2.0
|
||||||
|
|
||||||
|
mkdir -p $out/hosted/pub.dartlang.org
|
||||||
|
ln -s ${yaml} $out/hosted/pub.dartlang.org/yaml-3.1.1
|
||||||
|
'';
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pubspec_overrides.yaml
|
||||||
@@ -1,3 +1,73 @@
|
|||||||
|
## 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()`
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
- Fix some issues with running moxxmpp as a component
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
- **BREAKING**: Removed `connectAwaitable` and merged it with `connect`.
|
||||||
|
- **BREAKING**: Removed `allowPlainAuth` from `ConnectionSettings`. If you don't want to use SASL PLAIN, don't register the negotiator. If you want to only conditionally use SASL PLAIN, extend the `SaslPlainNegotiator` and override its `matchesFeature` method to only call the super method when SASL PLAIN should be used.
|
||||||
|
- **BREAKING**: The user avatar's `subscribe` and `unsubscribe` no longer subscribe to the `:data` PubSub nodes
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- **FIX**: Fix LMC not working.
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
- **FEAT**: Implement XEP-0308.
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
- **FEAT**: Message events now contain the stanza error, if available.
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
|
- **FIX**: Only stanza-id required 'sid:0' support.
|
||||||
|
- **FEAT**: Implement parsing and sending of retractions.
|
||||||
|
|
||||||
|
## 0.1.3+1
|
||||||
|
|
||||||
|
- **FIX**: Expose the error classes.
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
- **REFACTOR**: Replace MayFail by Result.
|
||||||
|
- **FIX**: Remove the old Results API.
|
||||||
|
- **FEAT**: Rework how the negotiator system works.
|
||||||
|
|
||||||
|
## 0.1.2+3
|
||||||
|
|
||||||
|
- **FIX**: SASL SCRAM-SHA-{256,512} should now work.
|
||||||
|
|
||||||
|
## 0.1.2+2
|
||||||
|
|
||||||
|
- **FIX**: Fix reconnections when the connection is awaited.
|
||||||
|
|
||||||
## 0.1.2+1
|
## 0.1.2+1
|
||||||
|
|
||||||
- **FIX**: A certificate rejection does not crash the connection.
|
- **FIX**: A certificate rejection does not crash the connection.
|
||||||
|
|||||||
@@ -2,6 +2,24 @@
|
|||||||
|
|
||||||
A pure-Dart XMPP library written for Moxxy.
|
A pure-Dart XMPP library written for Moxxy.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Include the following as a dependency in your pubspec file:
|
||||||
|
|
||||||
|
```
|
||||||
|
moxxmpp:
|
||||||
|
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
|
version: 0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find the documentation [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
See `./LICENSE`.
|
See `./LICENSE`.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you like what I do and you want to support me, feel free to donate to me on Ko-Fi.
|
||||||
|
|
||||||
|
[<img src="https://codeberg.org/moxxy/moxxyv2/raw/branch/master/assets/repo/kofi.png" height="36" style="height: 36px; border: 0px;"></img>](https://ko-fi.com/papatutuwawa)
|
||||||
@@ -1,7 +1,13 @@
|
|||||||
library moxxmpp;
|
library moxxmpp;
|
||||||
|
|
||||||
export 'package:moxxmpp/src/connection.dart';
|
export 'package:moxxmpp/src/connection.dart';
|
||||||
|
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/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/iq.dart';
|
||||||
export 'package:moxxmpp/src/jid.dart';
|
export 'package:moxxmpp/src/jid.dart';
|
||||||
export 'package:moxxmpp/src/managers/attributes.dart';
|
export 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
@@ -12,27 +18,30 @@ export 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
export 'package:moxxmpp/src/managers/priorities.dart';
|
export 'package:moxxmpp/src/managers/priorities.dart';
|
||||||
export 'package:moxxmpp/src/message.dart';
|
export 'package:moxxmpp/src/message.dart';
|
||||||
export 'package:moxxmpp/src/namespaces.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/namespaces.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/resource_binding.dart';
|
|
||||||
export 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
|
||||||
export 'package:moxxmpp/src/negotiators/sasl/plain.dart';
|
|
||||||
export 'package:moxxmpp/src/negotiators/sasl/scram.dart';
|
|
||||||
export 'package:moxxmpp/src/negotiators/starttls.dart';
|
|
||||||
export 'package:moxxmpp/src/ping.dart';
|
export 'package:moxxmpp/src/ping.dart';
|
||||||
export 'package:moxxmpp/src/presence.dart';
|
export 'package:moxxmpp/src/presence.dart';
|
||||||
export 'package:moxxmpp/src/reconnect.dart';
|
export 'package:moxxmpp/src/reconnect.dart';
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_2782.dart';
|
export 'package:moxxmpp/src/rfcs/rfc_2782.dart';
|
||||||
export 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
export 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
||||||
export 'package:moxxmpp/src/roster.dart';
|
export 'package:moxxmpp/src/rfcs/rfc_6120/resource_binding.dart';
|
||||||
|
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/errors.dart';
|
||||||
|
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/negotiator.dart';
|
||||||
|
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/plain.dart';
|
||||||
|
export 'package:moxxmpp/src/rfcs/rfc_6120/sasl/scram.dart';
|
||||||
|
export 'package:moxxmpp/src/rfcs/rfc_6120/starttls.dart';
|
||||||
|
export 'package:moxxmpp/src/roster/errors.dart';
|
||||||
|
export 'package:moxxmpp/src/roster/roster.dart';
|
||||||
|
export 'package:moxxmpp/src/roster/state.dart';
|
||||||
export 'package:moxxmpp/src/settings.dart';
|
export 'package:moxxmpp/src/settings.dart';
|
||||||
export 'package:moxxmpp/src/socket.dart';
|
export 'package:moxxmpp/src/socket.dart';
|
||||||
export 'package:moxxmpp/src/stanza.dart';
|
export 'package:moxxmpp/src/stanza.dart';
|
||||||
export 'package:moxxmpp/src/stringxml.dart';
|
export 'package:moxxmpp/src/stringxml.dart';
|
||||||
export 'package:moxxmpp/src/types/error.dart';
|
export 'package:moxxmpp/src/types/result.dart';
|
||||||
export 'package:moxxmpp/src/types/resultv2.dart';
|
export 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
@@ -57,11 +66,13 @@ export 'package:moxxmpp/src/xeps/xep_0203.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0280.dart';
|
export 'package:moxxmpp/src/xeps/xep_0280.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0297.dart';
|
export 'package:moxxmpp/src/xeps/xep_0297.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0308.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0333.dart';
|
export 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0334.dart';
|
export 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0352.dart';
|
export 'package:moxxmpp/src/xeps/xep_0352.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0359.dart';
|
export 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0363.dart';
|
export 'package:moxxmpp/src/xeps/xep_0363/errors.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0363/xep_0363.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0380.dart';
|
export 'package:moxxmpp/src/xeps/xep_0380.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0384/crypto.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/crypto.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0384/errors.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/errors.dart';
|
||||||
@@ -69,8 +80,15 @@ export 'package:moxxmpp/src/xeps/xep_0384/helpers.dart';
|
|||||||
export 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/types.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0384/xep_0384.dart';
|
export 'package:moxxmpp/src/xeps/xep_0384/xep_0384.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0385.dart';
|
export 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0414.dart';
|
export 'package:moxxmpp/src/xeps/xep_0386.dart';
|
||||||
|
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_0424.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0446.dart';
|
export 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0447.dart';
|
export 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0448.dart';
|
export 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||||
|
export 'package:moxxmpp/src/xeps/xep_0449.dart';
|
||||||
export 'package:moxxmpp/src/xeps/xep_0461.dart';
|
export 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// That means that when sending ```<iq to="example@some.server.example" id="abc123" />```,
|
||||||
|
/// the response stanza must be from "example@some.server.example", have id "abc123" and
|
||||||
|
/// be an iq stanza.
|
||||||
|
///
|
||||||
|
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
|
||||||
|
class StanzaAwaiter {
|
||||||
|
/// The pending stanzas, identified by their surrogate key.
|
||||||
|
final Map<_StanzaSurrogateKey, Completer<XMLNode>> _pending = {};
|
||||||
|
|
||||||
|
/// The critical section for accessing [StanzaAwaiter._pending].
|
||||||
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
|
/// Register a stanza as pending.
|
||||||
|
/// [to] is the value of the stanza's "to" attribute.
|
||||||
|
/// [id] is the value of the stanza's "id" attribute.
|
||||||
|
/// [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 {
|
||||||
|
final completer = await _lock.synchronized(() {
|
||||||
|
final completer = Completer<XMLNode>();
|
||||||
|
_pending[_StanzaSurrogateKey(to, id, tag)] = completer;
|
||||||
|
return completer;
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the stanza [stanza] is being awaited. [bareJid] is the bare JID of
|
||||||
|
/// the connection.
|
||||||
|
/// 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');
|
||||||
|
|
||||||
|
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(),
|
||||||
|
id,
|
||||||
|
stanza.tag,
|
||||||
|
);
|
||||||
|
|
||||||
|
return _lock.synchronized(() {
|
||||||
|
final completer = _pending[key];
|
||||||
|
if (completer != null) {
|
||||||
|
_pending.remove(key);
|
||||||
|
completer.complete(stanza);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
|
||||||
|
/// The reason a call to `XmppConnection.connect` failed.
|
||||||
|
abstract class XmppConnectionError extends XmppError {}
|
||||||
|
|
||||||
|
/// Returned by `XmppConnection.connect` when a negotiator returned an unrecoverable
|
||||||
|
/// error. Only returned when waitUntilLogin is true.
|
||||||
|
class NegotiatorReturnedError extends XmppConnectionError {
|
||||||
|
NegotiatorReturnedError(this.error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => error.isRecoverable();
|
||||||
|
|
||||||
|
/// The error returned by the negotiator.
|
||||||
|
final NegotiatorError error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class StreamFailureError extends XmppConnectionError {
|
||||||
|
StreamFailureError(this.error);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => error.isRecoverable();
|
||||||
|
|
||||||
|
/// The error that causes a connection failure.
|
||||||
|
final XmppError error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned by `XmppConnection.connect` when no connection could
|
||||||
|
/// be established.
|
||||||
|
class NoConnectionPossibleError extends XmppConnectionError {
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned if no matching authentication mechanism has been presented
|
||||||
|
class NoMatchingAuthenticationMechanismAvailableError
|
||||||
|
extends XmppConnectionError {
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned if no negotiator was picked, even though negotiations are not done
|
||||||
|
/// yet.
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/// This manager class is responsible to tell the moxxmpp XmppConnection
|
||||||
|
/// when a connection can be established or not, regarding the network availability.
|
||||||
|
abstract class ConnectivityManager {
|
||||||
|
/// Returns true if a network connection is available. If not, returns false.
|
||||||
|
Future<bool> hasConnection();
|
||||||
|
|
||||||
|
/// Returns a future that resolves once we have a network connection.
|
||||||
|
Future<void> waitForConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An implementation of [ConnectivityManager] that is always connected.
|
||||||
|
class AlwaysConnectedConnectivityManager extends ConnectivityManager {
|
||||||
|
@override
|
||||||
|
Future<bool> hasConnection() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> waitForConnection() async {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import 'package:moxxmpp/src/socket.dart';
|
||||||
|
|
||||||
|
/// An internal error class
|
||||||
|
// ignore: one_member_abstracts
|
||||||
|
abstract class XmppError {
|
||||||
|
/// Return true if we can recover from the error by attempting a reconnection.
|
||||||
|
bool isRecoverable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned if a socket error occured
|
||||||
|
class SocketError extends XmppError {
|
||||||
|
SocketError(this.event);
|
||||||
|
final XmppSocketErrorEvent event;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned if we time out
|
||||||
|
class TimeoutError extends XmppError {
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returned if we received a stream error
|
||||||
|
class StreamError extends XmppError {
|
||||||
|
// TODO(PapaTutuWawa): Be more precise
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.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/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_0030/types.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
import 'package:moxxmpp/src/xeps/xep_0084.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0385.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';
|
|
||||||
|
|
||||||
abstract class XmppEvent {}
|
abstract class XmppEvent {}
|
||||||
|
|
||||||
/// Triggered when the connection state of the XmppConnection has
|
/// Triggered when the connection state of the XmppConnection has
|
||||||
/// changed.
|
/// changed.
|
||||||
class ConnectionStateChangedEvent extends XmppEvent {
|
class ConnectionStateChangedEvent extends XmppEvent {
|
||||||
ConnectionStateChangedEvent(this.state, this.before, this.resumed);
|
ConnectionStateChangedEvent(this.state, this.before);
|
||||||
final XmppConnectionState before;
|
final XmppConnectionState before;
|
||||||
final XmppConnectionState state;
|
final XmppConnectionState state;
|
||||||
final bool resumed;
|
|
||||||
|
/// Indicates whether the connection state switched from a not connected state to a
|
||||||
|
/// connected state.
|
||||||
|
bool get connectionEstablished =>
|
||||||
|
before != XmppConnectionState.connected &&
|
||||||
|
state == XmppConnectionState.connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we encounter a stream error.
|
/// Triggered when we encounter a stream error.
|
||||||
class StreamErrorEvent extends XmppEvent {
|
class StreamErrorEvent extends XmppEvent {
|
||||||
StreamErrorEvent({ required this.error });
|
StreamErrorEvent({required this.error});
|
||||||
final String error;
|
final String error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,85 +41,100 @@ class AuthenticationFailedEvent extends XmppEvent {
|
|||||||
/// Triggered after the SASL authentication has succeeded.
|
/// Triggered after the SASL authentication has succeeded.
|
||||||
class AuthenticationSuccessEvent extends XmppEvent {}
|
class AuthenticationSuccessEvent extends XmppEvent {}
|
||||||
|
|
||||||
/// Triggered when we want to ping the connection open
|
|
||||||
class SendPingEvent extends XmppEvent {}
|
|
||||||
|
|
||||||
/// Triggered when the stream resumption was successful
|
/// Triggered when the stream resumption was successful
|
||||||
class StreamResumedEvent extends XmppEvent {
|
class StreamResumedEvent extends XmppEvent {
|
||||||
StreamResumedEvent({ required this.h });
|
StreamResumedEvent({required this.h});
|
||||||
final int h;
|
final int h;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when stream resumption failed
|
/// Triggered when stream resumption failed
|
||||||
class StreamResumeFailedEvent extends XmppEvent {}
|
class StreamResumeFailedEvent extends XmppEvent {}
|
||||||
|
|
||||||
|
/// Triggered when the roster has been modified
|
||||||
|
class RosterUpdatedEvent extends XmppEvent {
|
||||||
|
RosterUpdatedEvent(this.removed, this.modified, this.added);
|
||||||
|
|
||||||
|
/// A list of bare JIDs that are removed from the roster
|
||||||
|
final List<String> removed;
|
||||||
|
|
||||||
|
/// A list of XmppRosterItems that are modified. Can be correlated with one's cache
|
||||||
|
/// using the jid attribute.
|
||||||
|
final List<XmppRosterItem> modified;
|
||||||
|
|
||||||
|
/// A list of XmppRosterItems that are added to the roster.
|
||||||
|
final List<XmppRosterItem> added;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when a message is received
|
||||||
class MessageEvent extends XmppEvent {
|
class MessageEvent extends XmppEvent {
|
||||||
MessageEvent({
|
MessageEvent(
|
||||||
required this.body,
|
this.from,
|
||||||
required this.fromJid,
|
this.to,
|
||||||
required this.toJid,
|
this.id,
|
||||||
required this.sid,
|
this.encrypted,
|
||||||
required this.stanzaId,
|
this.extensions, {
|
||||||
required this.isCarbon,
|
|
||||||
required this.deliveryReceiptRequested,
|
|
||||||
required this.isMarkable,
|
|
||||||
required this.encrypted,
|
|
||||||
required this.other,
|
|
||||||
this.type,
|
this.type,
|
||||||
this.oob,
|
this.error,
|
||||||
this.sfs,
|
this.encryptionError,
|
||||||
this.sims,
|
|
||||||
this.reply,
|
|
||||||
this.chatState,
|
|
||||||
this.fun,
|
|
||||||
this.funReplacement,
|
|
||||||
this.funCancellation,
|
|
||||||
});
|
});
|
||||||
final String body;
|
|
||||||
final JID fromJid;
|
/// The from attribute of the message.
|
||||||
final JID toJid;
|
final JID from;
|
||||||
final String sid;
|
|
||||||
|
/// 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 String? type;
|
||||||
final StableStanzaId stanzaId;
|
|
||||||
final bool isCarbon;
|
final StanzaError? error;
|
||||||
final bool deliveryReceiptRequested;
|
|
||||||
final bool isMarkable;
|
/// Flag indicating whether the message was encrypted.
|
||||||
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 bool encrypted;
|
final bool encrypted;
|
||||||
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
|
/// Triggered when a client responds to our delivery receipt request
|
||||||
class DeliveryReceiptReceivedEvent extends XmppEvent {
|
class DeliveryReceiptReceivedEvent extends XmppEvent {
|
||||||
DeliveryReceiptReceivedEvent({ required this.from, required this.id });
|
DeliveryReceiptReceivedEvent({required this.from, required this.id});
|
||||||
final JID from;
|
final JID from;
|
||||||
final String id;
|
final String id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatMarkerEvent extends XmppEvent {
|
class ChatMarkerEvent extends XmppEvent {
|
||||||
ChatMarkerEvent({
|
ChatMarkerEvent(
|
||||||
required this.type,
|
this.from,
|
||||||
required this.from,
|
this.type,
|
||||||
required this.id,
|
this.id,
|
||||||
});
|
);
|
||||||
|
|
||||||
|
/// The entity that sent the chat marker.
|
||||||
final JID from;
|
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;
|
final String id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triggered when we received a Stream resumption ID
|
// Triggered when we received a Stream resumption ID
|
||||||
class StreamManagementEnabledEvent extends XmppEvent {
|
class StreamManagementEnabledEvent extends XmppEvent {
|
||||||
StreamManagementEnabledEvent({
|
StreamManagementEnabledEvent({
|
||||||
required this.resource,
|
required this.resource,
|
||||||
this.id,
|
this.id,
|
||||||
this.location,
|
this.location,
|
||||||
});
|
});
|
||||||
final String resource;
|
final String resource;
|
||||||
final String? id;
|
final String? id;
|
||||||
@@ -124,16 +142,11 @@ class StreamManagementEnabledEvent extends XmppEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we bound a resource
|
/// Triggered when we bound a resource
|
||||||
class ResourceBindingSuccessEvent extends XmppEvent {
|
class ResourceBoundEvent extends XmppEvent {
|
||||||
ResourceBindingSuccessEvent({ required this.resource });
|
ResourceBoundEvent(this.resource);
|
||||||
final String resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Triggered when we receive presence
|
/// The resource that was just bound.
|
||||||
class PresenceReceivedEvent extends XmppEvent {
|
final String resource;
|
||||||
PresenceReceivedEvent(this.jid, this.presence);
|
|
||||||
final JID jid;
|
|
||||||
final Stanza presence;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we are starting an connection attempt
|
/// Triggered when we are starting an connection attempt
|
||||||
@@ -149,21 +162,45 @@ class ServerItemDiscoEvent extends XmppEvent {
|
|||||||
|
|
||||||
/// Triggered when we receive a subscription request
|
/// Triggered when we receive a subscription request
|
||||||
class SubscriptionRequestReceivedEvent extends XmppEvent {
|
class SubscriptionRequestReceivedEvent extends XmppEvent {
|
||||||
SubscriptionRequestReceivedEvent({ required this.from });
|
SubscriptionRequestReceivedEvent({required this.from});
|
||||||
final JID from;
|
final JID from;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we receive a new or updated avatar
|
/// Triggered when we receive a new or updated avatar via XEP-0084
|
||||||
class AvatarUpdatedEvent extends XmppEvent {
|
class UserAvatarUpdatedEvent extends XmppEvent {
|
||||||
AvatarUpdatedEvent({ required this.jid, required this.base64, required this.hash });
|
UserAvatarUpdatedEvent(
|
||||||
final String jid;
|
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.base64,
|
||||||
|
this.hash,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The JID of the entity that updated their avatar.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// The base64-encoded avatar data.
|
||||||
final String base64;
|
final String base64;
|
||||||
|
|
||||||
|
/// The SHA-1 hash of the avatar.
|
||||||
final String hash;
|
final String hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when a PubSub notification has been received
|
/// Triggered when a PubSub notification has been received
|
||||||
class PubSubNotificationEvent extends XmppEvent {
|
class PubSubNotificationEvent extends XmppEvent {
|
||||||
PubSubNotificationEvent({ required this.item, required this.from });
|
PubSubNotificationEvent({required this.item, required this.from});
|
||||||
final PubSubItem item;
|
final PubSubItem item;
|
||||||
final String from;
|
final String from;
|
||||||
}
|
}
|
||||||
@@ -176,13 +213,13 @@ class StanzaAckedEvent extends XmppEvent {
|
|||||||
|
|
||||||
/// Triggered when receiving a push of the blocklist
|
/// Triggered when receiving a push of the blocklist
|
||||||
class BlocklistBlockPushEvent extends XmppEvent {
|
class BlocklistBlockPushEvent extends XmppEvent {
|
||||||
BlocklistBlockPushEvent({ required this.items });
|
BlocklistBlockPushEvent({required this.items});
|
||||||
final List<String> items;
|
final List<String> items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when receiving a push of the blocklist
|
/// Triggered when receiving a push of the blocklist
|
||||||
class BlocklistUnblockPushEvent extends XmppEvent {
|
class BlocklistUnblockPushEvent extends XmppEvent {
|
||||||
BlocklistUnblockPushEvent({ required this.items });
|
BlocklistUnblockPushEvent({required this.items});
|
||||||
final List<String> items;
|
final List<String> items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,3 +241,21 @@ class OmemoDeviceListUpdatedEvent extends XmppEvent {
|
|||||||
final JID jid;
|
final JID jid;
|
||||||
final List<int> deviceList;
|
final List<int> deviceList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Triggered when a reconnection is not performed due to a non-recoverable
|
||||||
|
/// error.
|
||||||
|
class NonRecoverableErrorEvent extends XmppEvent {
|
||||||
|
NonRecoverableErrorEvent(this.error);
|
||||||
|
|
||||||
|
/// The error in question.
|
||||||
|
final XmppError error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when the stream negotiations are done.
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,33 @@
|
|||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
|
||||||
bool handleUnhandledStanza(XmppConnection conn, Stanza stanza) {
|
/// Bounce a stanza if it was not handled by any manager. [conn] is the connection object
|
||||||
if (stanza.type != 'error' && stanza.type != 'result') {
|
/// to use for sending the stanza. [data] is the StanzaHandlerData of the unhandled
|
||||||
conn.sendStanza(stanza.errorReply('cancel', 'feature-not-implemented'));
|
/// stanza.
|
||||||
}
|
Future<void> handleUnhandledStanza(
|
||||||
|
XmppConnection conn,
|
||||||
|
StanzaHandlerData data,
|
||||||
|
) async {
|
||||||
|
if (data.stanza.type != 'error' && data.stanza.type != 'result') {
|
||||||
|
final stanza = data.stanza.copyWith(
|
||||||
|
to: data.stanza.from,
|
||||||
|
from: data.stanza.to,
|
||||||
|
type: 'error',
|
||||||
|
children: [
|
||||||
|
buildErrorElement(
|
||||||
|
'cancel',
|
||||||
|
'feature-not-implemented',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
await conn.sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
stanza,
|
||||||
|
awaitable: false,
|
||||||
|
forceEncryption: data.encrypted,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,83 +1,84 @@
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
/// Represents a Jabber ID in parsed form.
|
||||||
@immutable
|
@immutable
|
||||||
class JID {
|
class JID {
|
||||||
|
|
||||||
const JID(this.local, this.domain, this.resource);
|
const JID(this.local, this.domain, this.resource);
|
||||||
|
|
||||||
|
/// Parses the string [jid] into a JID instance.
|
||||||
factory JID.fromString(String jid) {
|
factory JID.fromString(String jid) {
|
||||||
// 0: Parsing either the local or domain part
|
// Algorithm taken from here: https://blog.samwhited.com/2021/02/xmpp-addresses/
|
||||||
// 1: Parsing the domain part
|
var localPart = '';
|
||||||
// 2: Parsing the resource
|
var domainPart = '';
|
||||||
var state = 0;
|
var resourcePart = '';
|
||||||
var buffer = '';
|
|
||||||
var local_ = '';
|
|
||||||
var domain_ = '';
|
|
||||||
var resource_ = '';
|
|
||||||
|
|
||||||
for (var i = 0; i < jid.length; i++) {
|
final slashParts = jid.split('/');
|
||||||
final c = jid[i];
|
if (slashParts.length == 1) {
|
||||||
final eol = i == jid.length - 1;
|
resourcePart = '';
|
||||||
|
} else {
|
||||||
|
resourcePart = slashParts.sublist(1).join('/');
|
||||||
|
|
||||||
switch (state) {
|
assert(
|
||||||
case 0: {
|
resourcePart.isNotEmpty,
|
||||||
if (c == '@') {
|
'Resource part cannot be there and empty',
|
||||||
local_ = buffer;
|
);
|
||||||
buffer = '';
|
|
||||||
state = 1;
|
|
||||||
} else if (c == '/') {
|
|
||||||
domain_ = buffer;
|
|
||||||
buffer = '';
|
|
||||||
state = 2;
|
|
||||||
} else if (eol) {
|
|
||||||
domain_ = buffer + c;
|
|
||||||
} else {
|
|
||||||
buffer += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1: {
|
|
||||||
if (c == '/') {
|
|
||||||
domain_ = buffer;
|
|
||||||
buffer = '';
|
|
||||||
state = 2;
|
|
||||||
} else if (eol) {
|
|
||||||
domain_ = buffer;
|
|
||||||
|
|
||||||
if (c != ' ') {
|
|
||||||
domain_ = domain_ + c;
|
|
||||||
}
|
|
||||||
} else if (c != ' ') {
|
|
||||||
buffer += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2: {
|
|
||||||
if (eol) {
|
|
||||||
resource_ = buffer;
|
|
||||||
|
|
||||||
if (c != ' ') {
|
|
||||||
resource_ = resource_ + c;
|
|
||||||
}
|
|
||||||
} else if (c != ''){
|
|
||||||
buffer += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JID(local_, domain_, resource_);
|
final atParts = slashParts.first.split('@');
|
||||||
|
if (atParts.length == 1) {
|
||||||
|
localPart = '';
|
||||||
|
domainPart = atParts.first;
|
||||||
|
} else {
|
||||||
|
localPart = atParts.first;
|
||||||
|
domainPart = atParts.sublist(1).join('@');
|
||||||
|
|
||||||
|
assert(localPart.isNotEmpty, 'Local part cannot be there and empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
return JID(
|
||||||
|
localPart,
|
||||||
|
domainPart.endsWith('.')
|
||||||
|
? domainPart.substring(0, domainPart.length - 1)
|
||||||
|
: domainPart,
|
||||||
|
resourcePart,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
final String local;
|
final String local;
|
||||||
final String domain;
|
final String domain;
|
||||||
final String resource;
|
final String resource;
|
||||||
|
|
||||||
|
/// Returns true if the JID is bare.
|
||||||
bool isBare() => resource.isEmpty;
|
bool isBare() => resource.isEmpty;
|
||||||
|
|
||||||
|
/// Returns true if the JID is full.
|
||||||
bool isFull() => resource.isNotEmpty;
|
bool isFull() => resource.isNotEmpty;
|
||||||
|
|
||||||
JID toBare() => JID(local, domain, '');
|
/// 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);
|
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.
|
||||||
|
bool bareCompare(JID other, {bool ensureBare = false}) {
|
||||||
|
if (ensureBare && !other.isBare()) return false;
|
||||||
|
|
||||||
|
return local == other.local && domain == other.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to JID instance into its string representation of
|
||||||
|
/// localpart@domainpart/resource.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
var result = '';
|
var result = '';
|
||||||
@@ -97,7 +98,9 @@ class JID {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other is JID) {
|
if (other is JID) {
|
||||||
return other.local == local && other.domain == domain && other.resource == resource;
|
return other.local == local &&
|
||||||
|
other.domain == domain &&
|
||||||
|
other.resource == resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
@@ -11,21 +10,20 @@ import 'package:moxxmpp/src/stanza.dart';
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
class XmppManagerAttributes {
|
class XmppManagerAttributes {
|
||||||
|
|
||||||
XmppManagerAttributes({
|
XmppManagerAttributes({
|
||||||
required this.sendStanza,
|
required this.sendStanza,
|
||||||
required this.sendNonza,
|
required this.sendNonza,
|
||||||
required this.getManagerById,
|
required this.getManagerById,
|
||||||
required this.sendEvent,
|
required this.sendEvent,
|
||||||
required this.getConnectionSettings,
|
required this.getConnectionSettings,
|
||||||
required this.isFeatureSupported,
|
|
||||||
required this.getFullJID,
|
required this.getFullJID,
|
||||||
required this.getSocket,
|
required this.getSocket,
|
||||||
required this.getConnection,
|
required this.getConnection,
|
||||||
required this.getNegotiatorById,
|
required this.getNegotiatorById,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Send a stanza whose response can be awaited.
|
/// Send a stanza whose response can be awaited.
|
||||||
final Future<XMLNode> Function(Stanza stanza, { StanzaFromType addFrom, bool addId, bool awaitable, bool encrypted}) sendStanza;
|
final Future<XMLNode?> Function(StanzaDetails) sendStanza;
|
||||||
|
|
||||||
/// Send a nonza.
|
/// Send a nonza.
|
||||||
final void Function(XMLNode) sendNonza;
|
final void Function(XMLNode) sendNonza;
|
||||||
@@ -39,9 +37,6 @@ class XmppManagerAttributes {
|
|||||||
/// (Maybe) Get a Manager attached to the connection by its Id.
|
/// (Maybe) Get a Manager attached to the connection by its Id.
|
||||||
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
||||||
|
|
||||||
/// Returns true if a server feature is supported
|
|
||||||
final bool Function(String) isFeatureSupported;
|
|
||||||
|
|
||||||
/// Returns the full JID of the current account
|
/// Returns the full JID of the current account
|
||||||
final JID Function() getFullJID;
|
final JID Function() getFullJID;
|
||||||
|
|
||||||
@@ -51,5 +46,6 @@ class XmppManagerAttributes {
|
|||||||
/// Return the [XmppConnection] the manager is registered against.
|
/// Return the [XmppConnection] the manager is registered against.
|
||||||
final XmppConnection Function() getConnection;
|
final XmppConnection Function() getConnection;
|
||||||
|
|
||||||
final T? Function<T extends XmppFeatureNegotiatorBase>(String) getNegotiatorById;
|
final T? Function<T extends XmppFeatureNegotiatorBase>(String)
|
||||||
|
getNegotiatorById;
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,30 @@
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/managers/handlers.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/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
|
|
||||||
abstract class XmppManagerBase {
|
abstract class XmppManagerBase {
|
||||||
|
XmppManagerBase(this.id);
|
||||||
|
|
||||||
late final XmppManagerAttributes _managerAttributes;
|
late final XmppManagerAttributes _managerAttributes;
|
||||||
late final Logger _log;
|
late final Logger _log;
|
||||||
|
|
||||||
|
/// Flag indicating that the post registration callback has been called once.
|
||||||
|
bool initialized = false;
|
||||||
|
|
||||||
/// Registers the callbacks from XmppConnection with the manager
|
/// Registers the callbacks from XmppConnection with the manager
|
||||||
void register(XmppManagerAttributes attributes) {
|
void register(XmppManagerAttributes attributes) {
|
||||||
_managerAttributes = attributes;
|
_managerAttributes = attributes;
|
||||||
_log = Logger(getName());
|
_log = Logger(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the attributes that are registered with the manager.
|
/// Returns the attributes that are registered with the manager.
|
||||||
@@ -20,29 +33,65 @@ abstract class XmppManagerBase {
|
|||||||
return _managerAttributes;
|
return _managerAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves to true when the server supports the disco feature [xmlns]. Resolves
|
||||||
|
/// to false when either the disco request fails or the server does not
|
||||||
|
/// support [xmlns].
|
||||||
|
/// Note that this function requires a registered DiscoManager.
|
||||||
|
@protected
|
||||||
|
Future<bool> isFeatureSupported(String xmlns) async {
|
||||||
|
final dm = _managerAttributes.getManagerById<DiscoManager>(discoManager);
|
||||||
|
assert(
|
||||||
|
dm != null,
|
||||||
|
'The DiscoManager must be registered for isFeatureSupported to work',
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await dm!.discoInfoQuery(
|
||||||
|
_managerAttributes.getConnectionSettings().jid.toDomain(),
|
||||||
|
shouldEncrypt: false,
|
||||||
|
);
|
||||||
|
if (result.isType<DiscoError>()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.get<DiscoInfo>().features.contains(xmlns);
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
||||||
/// send. These are run before the stanza is sent.
|
/// send. These are run before the stanza is sent. The higher the value of the
|
||||||
|
/// handler's priority, the earlier it is run.
|
||||||
List<StanzaHandler> getOutgoingPreStanzaHandlers() => [];
|
List<StanzaHandler> getOutgoingPreStanzaHandlers() => [];
|
||||||
|
|
||||||
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
||||||
/// send. These are run after the stanza is sent.
|
/// send. These are run after the stanza is sent. The higher the value of the
|
||||||
|
/// handler's priority, the earlier it is run.
|
||||||
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [];
|
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [];
|
||||||
|
|
||||||
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
/// Return the StanzaHandlers associated with this manager that deal with stanzas we
|
||||||
/// receive.
|
/// receive. The higher the value of the
|
||||||
|
/// handler's priority, the earlier it is run.
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [];
|
List<StanzaHandler> getIncomingStanzaHandlers() => [];
|
||||||
|
|
||||||
/// Return the NonzaHandlers associated with this manager.
|
/// Return the StanzaHandlers associated with this manager that deal with stanza handlers
|
||||||
|
/// that have to run before the main ones run. This is useful, for example, for OMEMO
|
||||||
|
/// as we have to decrypt the stanza before we do anything else. The higher the value
|
||||||
|
/// of the handler's priority, the earlier it is run.
|
||||||
|
List<StanzaHandler> getIncomingPreStanzaHandlers() => [];
|
||||||
|
|
||||||
|
/// Return the NonzaHandlers associated with this manager. The higher the value of the
|
||||||
|
/// handler's priority, the earlier it is run.
|
||||||
List<NonzaHandler> getNonzaHandlers() => [];
|
List<NonzaHandler> getNonzaHandlers() => [];
|
||||||
|
|
||||||
/// Return a list of features that should be included in a disco response.
|
/// Return a list of features that should be included in a disco response.
|
||||||
List<String> getDiscoFeatures() => [];
|
List<String> getDiscoFeatures() => [];
|
||||||
|
|
||||||
/// Return the Id (akin to xmlns) of this manager.
|
/// Return a list of identities that should be included in a disco response.
|
||||||
String getId();
|
List<Identity> getDiscoIdentities() => [];
|
||||||
|
|
||||||
/// Return a name that will be used for logging.
|
/// Return the Id (akin to xmlns) of this manager.
|
||||||
String getName();
|
final String id;
|
||||||
|
|
||||||
|
/// The name of the manager.
|
||||||
|
String get name => toString();
|
||||||
|
|
||||||
/// Return the logger for this manager.
|
/// Return the logger for this manager.
|
||||||
Logger get logger => _log;
|
Logger get logger => _log;
|
||||||
@@ -53,20 +102,75 @@ abstract class XmppManagerBase {
|
|||||||
/// Returns true if the XEP is supported on the server. If not, returns false
|
/// Returns true if the XEP is supported on the server. If not, returns false
|
||||||
Future<bool> isSupported();
|
Future<bool> isSupported();
|
||||||
|
|
||||||
|
/// Called after the registration of all managers against the XmppConnection is done.
|
||||||
|
/// This method is only called once during the entire lifetime of it.
|
||||||
|
@mustCallSuper
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
final disco = getAttributes().getManagerById<DiscoManager>(discoManager);
|
||||||
|
if (disco != null) {
|
||||||
|
if (getDiscoFeatures().isNotEmpty) {
|
||||||
|
disco.addFeatures(getDiscoFeatures());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getDiscoIdentities().isNotEmpty) {
|
||||||
|
disco.addIdentities(getDiscoIdentities());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if
|
/// Runs all NonzaHandlers of this Manager which match the nonza. Resolves to true if
|
||||||
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
|
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
|
||||||
Future<bool> runNonzaHandlers(XMLNode nonza) async {
|
Future<bool> runNonzaHandlers(XMLNode nonza) async {
|
||||||
var handled = false;
|
var handled = false;
|
||||||
await Future.forEach(
|
await Future.forEach(getNonzaHandlers(), (NonzaHandler handler) async {
|
||||||
getNonzaHandlers(),
|
if (handler.matches(nonza)) {
|
||||||
(NonzaHandler handler) async {
|
handled = true;
|
||||||
if (handler.matches(nonza)) {
|
await handler.callback(nonza);
|
||||||
handled = true;
|
|
||||||
await handler.callback(nonza);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true, if the current stream negotiations resulted in a new stream. Useful
|
||||||
|
/// for plugins to reset their cache in case of a new stream.
|
||||||
|
/// The value only makes sense after receiving a StreamNegotiationsDoneEvent.
|
||||||
|
Future<bool> isNewStream() async {
|
||||||
|
final sm =
|
||||||
|
getAttributes().getManagerById<StreamManagementManager>(smManager);
|
||||||
|
|
||||||
|
return sm?.streamResumed == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a reply of the stanza in [data] with [type]. Replaces the original stanza's
|
||||||
|
/// children with [children].
|
||||||
|
///
|
||||||
|
/// Note that this function currently only accepts IQ stanzas.
|
||||||
|
Future<void> reply(
|
||||||
|
StanzaHandlerData data,
|
||||||
|
String type,
|
||||||
|
List<XMLNode> children,
|
||||||
|
) async {
|
||||||
|
assert(
|
||||||
|
data.stanza.tag == 'iq',
|
||||||
|
'Reply makes little sense for non-IQ stanzas',
|
||||||
|
);
|
||||||
|
|
||||||
|
final stanza = data.stanza.copyWith(
|
||||||
|
to: data.stanza.from,
|
||||||
|
from: data.stanza.to,
|
||||||
|
type: type,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
stanza,
|
||||||
|
awaitable: false,
|
||||||
|
forceEncryption: data.encrypted,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,60 +1,47 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
import 'package:moxxmpp/src/util/typed_map.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_0446.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
|
||||||
|
|
||||||
part 'data.freezed.dart';
|
abstract class StanzaHandlerExtension {}
|
||||||
|
|
||||||
@freezed
|
class StanzaHandlerData {
|
||||||
class StanzaHandlerData with _$StanzaHandlerData {
|
StanzaHandlerData(
|
||||||
factory StanzaHandlerData(
|
this.done,
|
||||||
// Indicates to the runner that processing is now done. This means that all
|
this.cancel,
|
||||||
// pre-processing is done and no other handlers should be consulted.
|
this.stanza,
|
||||||
bool done,
|
this.extensions, {
|
||||||
// Indicates to the runner that processing is to be cancelled and no further handlers
|
this.cancelReason,
|
||||||
// should run. The stanza also will not be sent.
|
this.encryptionError,
|
||||||
bool cancel,
|
this.encrypted = false,
|
||||||
// The reason why we cancelled the processing and sending
|
this.forceEncryption = false,
|
||||||
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
|
/// Indicates to the runner that processing is now done. This means that all
|
||||||
Stanza stanza,
|
/// pre-processing is done and no other handlers should be consulted.
|
||||||
{
|
bool done;
|
||||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
|
||||||
// stanza handlers. MUST NOT be overwritten.
|
/// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||||
@Default(false) bool retransmitted,
|
/// should run. The stanza also will not be sent.
|
||||||
StatelessMediaSharingData? sims,
|
bool cancel;
|
||||||
StatelessFileSharingData? sfs,
|
|
||||||
OOBData? oob,
|
/// The reason why we cancelled the processing and sending.
|
||||||
StableStanzaId? stableId,
|
Object? cancelReason;
|
||||||
ReplyData? reply,
|
|
||||||
ChatState? chatState,
|
/// The reason why an encryption or decryption failed.
|
||||||
@Default(false) bool isCarbon,
|
Object? encryptionError;
|
||||||
@Default(false) bool deliveryReceiptRequested,
|
|
||||||
@Default(false) bool isMarkable,
|
/// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is
|
||||||
// File Upload Notifications
|
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
||||||
// A notification
|
Stanza stanza;
|
||||||
FileMetadataData? fun,
|
|
||||||
// The stanza id this replaces
|
/// Whether the stanza was received encrypted
|
||||||
String? funReplacement,
|
bool encrypted;
|
||||||
// The stanza id this cancels
|
|
||||||
String? funCancellation,
|
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
// Whether the stanza was received encrypted
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
@Default(false) bool encrypted,
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
// The stated type of encryption used, if any was used
|
// to the JID anyway.
|
||||||
ExplicitEncryptionType? encryptionType,
|
bool forceEncryption;
|
||||||
// Delayed Delivery
|
|
||||||
DelayedDelivery? delayedDelivery,
|
/// Additional data from other managers.
|
||||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
final TypedMap<StanzaHandlerExtension> extensions;
|
||||||
// pass data around.
|
|
||||||
@Default(<String, dynamic>{}) Map<String, dynamic> other,
|
|
||||||
}
|
|
||||||
) = _StanzaHandlerData;
|
|
||||||
}
|
}
|
||||||
@@ -1,613 +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; // 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;
|
|
||||||
|
|
||||||
@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,
|
|
||||||
ExplicitEncryptionType? encryptionType,
|
|
||||||
DelayedDelivery? delayedDelivery,
|
|
||||||
Map<String, dynamic> other});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @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? encryptionType = freezed,
|
|
||||||
Object? delayedDelivery = freezed,
|
|
||||||
Object? other = 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,
|
|
||||||
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>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @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,
|
|
||||||
ExplicitEncryptionType? encryptionType,
|
|
||||||
DelayedDelivery? delayedDelivery,
|
|
||||||
Map<String, dynamic> other});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @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? encryptionType = freezed,
|
|
||||||
Object? delayedDelivery = freezed,
|
|
||||||
Object? other = 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,
|
|
||||||
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>,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @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.encryptionType,
|
|
||||||
this.delayedDelivery,
|
|
||||||
final Map<String, dynamic> other = const <String, dynamic>{}})
|
|
||||||
: _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;
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@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.encryptionType, encryptionType) &&
|
|
||||||
const DeepCollectionEquality()
|
|
||||||
.equals(other.delayedDelivery, delayedDelivery) &&
|
|
||||||
const DeepCollectionEquality().equals(other._other, this._other));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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(encryptionType),
|
|
||||||
const DeepCollectionEquality().hash(delayedDelivery),
|
|
||||||
const DeepCollectionEquality().hash(_other)
|
|
||||||
]);
|
|
||||||
|
|
||||||
@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 ExplicitEncryptionType? encryptionType,
|
|
||||||
final DelayedDelivery? delayedDelivery,
|
|
||||||
final Map<String, dynamic> other}) = _$_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 // 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
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
@@ -4,90 +4,111 @@ import 'package:moxxmpp/src/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
/// 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 {
|
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].
|
/// 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) {
|
bool matches(XMLNode node) {
|
||||||
var matches = false;
|
var matches = true;
|
||||||
|
|
||||||
if (nonzaTag == null && nonzaXmlns == null) {
|
if (nonzaTag == null && nonzaXmlns == null) {
|
||||||
matches = true;
|
return true;
|
||||||
}
|
} else {
|
||||||
|
if (nonzaXmlns != null) {
|
||||||
if (nonzaXmlns != null && nonzaTag != null) {
|
matches &= node.attributes['xmlns'] == nonzaXmlns;
|
||||||
matches = (node.attributes['xmlns'] ?? '') == nonzaXmlns! && node.tag == nonzaTag!;
|
}
|
||||||
}
|
if (nonzaTag != null) {
|
||||||
|
matches &= node.tag == nonzaTag;
|
||||||
if (matchStanzas && nonzaTag == null) {
|
}
|
||||||
matches = [ 'iq', 'presence', 'message' ].contains(node.tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NonzaHandler extends Handler {
|
/// A Handler that only matches stanzas.
|
||||||
|
|
||||||
NonzaHandler({
|
|
||||||
required this.callback,
|
|
||||||
String? nonzaTag,
|
|
||||||
String? nonzaXmlns,
|
|
||||||
}) : super(
|
|
||||||
false,
|
|
||||||
nonzaTag: nonzaTag,
|
|
||||||
nonzaXmlns: nonzaXmlns,
|
|
||||||
);
|
|
||||||
final Future<bool> Function(XMLNode) callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
class StanzaHandler extends Handler {
|
class StanzaHandler extends Handler {
|
||||||
|
|
||||||
StanzaHandler({
|
StanzaHandler({
|
||||||
required this.callback,
|
required this.callback,
|
||||||
this.tagXmlns,
|
this.tagXmlns,
|
||||||
this.tagName,
|
this.tagName,
|
||||||
this.priority = 0,
|
this.priority = 0,
|
||||||
String? stanzaTag,
|
this.stanzaTag,
|
||||||
}) : super(
|
this.xmlns,
|
||||||
true,
|
});
|
||||||
nonzaTag: stanzaTag,
|
|
||||||
nonzaXmlns: stanzaXmlns,
|
/// If specified, then the stanza must contain a direct child with a tag equal to
|
||||||
);
|
/// [tagName].
|
||||||
final String? 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;
|
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;
|
final int priority;
|
||||||
|
|
||||||
|
/// The function to call when a stanza matches the description.
|
||||||
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
|
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(XMLNode node) {
|
bool matches(XMLNode node) {
|
||||||
var matches = super.matches(node);
|
var matches = ['iq', 'message', 'presence'].contains(node.tag);
|
||||||
|
if (stanzaTag != null) {
|
||||||
if (matches == false) {
|
matches &= node.tag == stanzaTag;
|
||||||
return false;
|
}
|
||||||
|
if (xmlns != null) {
|
||||||
|
matches &= node.xmlns == xmlns;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagName != null) {
|
if (tagName != null) {
|
||||||
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
|
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
|
||||||
|
matches &= firstTag != null;
|
||||||
|
|
||||||
matches = firstTag != null;
|
if (tagXmlns != null) {
|
||||||
|
matches &= firstTag?.xmlns == tagXmlns;
|
||||||
|
}
|
||||||
} else if (tagXmlns != null) {
|
} else if (tagXmlns != null) {
|
||||||
return listContains(
|
matches &= listContains(
|
||||||
node.children,
|
node.children,
|
||||||
(XMLNode node_) => node_.attributes.containsKey('xmlns') && node_.attributes['xmlns'] == tagXmlns,
|
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagName == null && tagXmlns == null) {
|
|
||||||
matches = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) => b.priority.compareTo(a.priority);
|
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) =>
|
||||||
|
b.priority.compareTo(a.priority);
|
||||||
@@ -1,26 +1,34 @@
|
|||||||
const smManager = 'im.moxxmpp.streammangementmanager';
|
const smManager = 'org.moxxmpp.streammangementmanager';
|
||||||
const discoManager = 'im.moxxmpp.discomanager';
|
const discoManager = 'org.moxxmpp.discomanager';
|
||||||
const messageManager = 'im.moxxmpp.messagemanager';
|
const messageManager = 'org.moxxmpp.messagemanager';
|
||||||
const rosterManager = 'im.moxxmpp.rostermanager';
|
const rosterManager = 'org.moxxmpp.rostermanager';
|
||||||
const presenceManager = 'im.moxxmpp.presencemanager';
|
const presenceManager = 'org.moxxmpp.presencemanager';
|
||||||
const csiManager = 'im.moxxmpp.csimanager';
|
const csiManager = 'org.moxxmpp.csimanager';
|
||||||
const carbonsManager = 'im.moxxmpp.carbonsmanager';
|
const carbonsManager = 'org.moxxmpp.carbonsmanager';
|
||||||
const vcardManager = 'im.moxxmpp.vcardmanager';
|
const vcardManager = 'org.moxxmpp.vcardmanager';
|
||||||
const pubsubManager = 'im.moxxmpp.pubsubmanager';
|
const pubsubManager = 'org.moxxmpp.pubsubmanager';
|
||||||
const userAvatarManager = 'im.moxxmpp.useravatarmanager';
|
const userAvatarManager = 'org.moxxmpp.useravatarmanager';
|
||||||
const stableIdManager = 'im.moxxmpp.stableidmanager';
|
const stableIdManager = 'org.moxxmpp.stableidmanager';
|
||||||
const simsManager = 'im.moxxmpp.simsmanager';
|
const simsManager = 'org.moxxmpp.simsmanager';
|
||||||
const messageDeliveryReceiptManager = 'im.moxxmpp.messagedeliveryreceiptmanager';
|
const messageDeliveryReceiptManager =
|
||||||
const chatMarkerManager = 'im.moxxmpp.chatmarkermanager';
|
'org.moxxmpp.messagedeliveryreceiptmanager';
|
||||||
const oobManager = 'im.moxxmpp.oobmanager';
|
const chatMarkerManager = 'org.moxxmpp.chatmarkermanager';
|
||||||
const sfsManager = 'im.moxxmpp.sfsmanager';
|
const oobManager = 'org.moxxmpp.oobmanager';
|
||||||
const messageRepliesManager = 'im.moxxmpp.messagerepliesmanager';
|
const sfsManager = 'org.moxxmpp.sfsmanager';
|
||||||
const blockingManager = 'im.moxxmpp.blockingmanager';
|
const messageRepliesManager = 'org.moxxmpp.messagerepliesmanager';
|
||||||
const httpFileUploadManager = 'im.moxxmpp.httpfileuploadmanager';
|
const blockingManager = 'org.moxxmpp.blockingmanager';
|
||||||
const chatStateManager = 'im.moxxmpp.chatstatemanager';
|
const httpFileUploadManager = 'org.moxxmpp.httpfileuploadmanager';
|
||||||
const pingManager = 'im.moxxmpp.ping';
|
const chatStateManager = 'org.moxxmpp.chatstatemanager';
|
||||||
const fileUploadNotificationManager = 'im.moxxmpp.fileuploadnotificationmanager';
|
const pingManager = 'org.moxxmpp.ping';
|
||||||
|
const fileUploadNotificationManager =
|
||||||
|
'org.moxxmpp.fileuploadnotificationmanager';
|
||||||
const omemoManager = 'org.moxxmpp.omemomanager';
|
const omemoManager = 'org.moxxmpp.omemomanager';
|
||||||
const emeManager = 'org.moxxmpp.ememanager';
|
const emeManager = 'org.moxxmpp.ememanager';
|
||||||
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
|
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
|
||||||
const delayedDeliveryManager = 'org.moxxmpp.delayeddeliverymanager';
|
const delayedDeliveryManager = 'org.moxxmpp.delayeddeliverymanager';
|
||||||
|
const messageRetractionManager = 'org.moxxmpp.messageretractionmanager';
|
||||||
|
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';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,222 +1,144 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/staging/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_0066.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0184.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0447.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';
|
||||||
|
|
||||||
class MessageDetails {
|
/// 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>,
|
||||||
|
);
|
||||||
|
|
||||||
const MessageDetails({
|
/// The raw content of the <body /> element.
|
||||||
required this.to,
|
class MessageBodyData implements StanzaHandlerExtension {
|
||||||
this.body,
|
const MessageBodyData(this.body);
|
||||||
this.requestDeliveryReceipt = false,
|
|
||||||
this.requestChatMarkers = true,
|
/// The content of the <body /> element.
|
||||||
this.id,
|
|
||||||
this.originId,
|
|
||||||
this.quoteBody,
|
|
||||||
this.quoteId,
|
|
||||||
this.quoteFrom,
|
|
||||||
this.chatState,
|
|
||||||
this.sfs,
|
|
||||||
this.fun,
|
|
||||||
this.funReplacement,
|
|
||||||
this.funCancellation,
|
|
||||||
this.shouldEncrypt = false,
|
|
||||||
});
|
|
||||||
final String to;
|
|
||||||
final String? body;
|
final String? body;
|
||||||
final bool requestDeliveryReceipt;
|
|
||||||
final bool requestChatMarkers;
|
XMLNode toXML() {
|
||||||
final String? id;
|
return XMLNode(
|
||||||
final String? originId;
|
tag: 'body',
|
||||||
final String? quoteBody;
|
text: body,
|
||||||
final String? quoteId;
|
);
|
||||||
final String? quoteFrom;
|
}
|
||||||
final ChatState? chatState;
|
}
|
||||||
final StatelessFileSharingData? sfs;
|
|
||||||
final FileMetadataData? fun;
|
/// The id attribute of the message stanza.
|
||||||
final String? funReplacement;
|
class MessageIdData implements StanzaHandlerExtension {
|
||||||
final String? funCancellation;
|
const MessageIdData(this.id);
|
||||||
final bool shouldEncrypt;
|
|
||||||
|
/// The id attribute of the stanza.
|
||||||
|
final String id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageManager extends XmppManagerBase {
|
class MessageManager extends XmppManagerBase {
|
||||||
@override
|
MessageManager() : super(messageManager);
|
||||||
String getId() => messageManager;
|
|
||||||
|
|
||||||
@override
|
/// The priority of the message handler. If a handler should run before this one,
|
||||||
String getName() => 'MessageManager';
|
/// 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
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'message',
|
stanzaTag: 'message',
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
priority: -100,
|
priority: messageHandlerPriority,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(Stanza _, StanzaHandlerData state) async {
|
Future<StanzaHandlerData> _onMessage(
|
||||||
final message = state.stanza;
|
Stanza _,
|
||||||
final body = message.firstTag('body');
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
getAttributes().sendEvent(MessageEvent(
|
getAttributes().sendEvent(
|
||||||
body: body != null ? body.innerText() : '',
|
MessageEvent(
|
||||||
fromJid: JID.fromString(message.attributes['from']! as String),
|
JID.fromString(state.stanza.attributes['from']! as String),
|
||||||
toJid: JID.fromString(message.attributes['to']! as String),
|
JID.fromString(state.stanza.attributes['to']! as String),
|
||||||
sid: message.attributes['id']! as String,
|
state.stanza.attributes['id']! as String,
|
||||||
stanzaId: state.stableId ?? const StableStanzaId(),
|
state.encrypted,
|
||||||
isCarbon: state.isCarbon,
|
state.extensions,
|
||||||
deliveryReceiptRequested: state.deliveryReceiptRequested,
|
type: state.stanza.attributes['type'] as String?,
|
||||||
isMarkable: state.isMarkable,
|
error: StanzaError.fromStanza(state.stanza),
|
||||||
type: message.attributes['type'] as String?,
|
encryptionError: state.encryptionError,
|
||||||
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,
|
|
||||||
other: state.other,
|
|
||||||
),);
|
|
||||||
|
|
||||||
return state.copyWith(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) {
|
|
||||||
final stanza = Stanza.message(
|
|
||||||
to: details.to,
|
|
||||||
type: 'chat',
|
|
||||||
id: details.id,
|
|
||||||
children: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (details.quoteBody != null) {
|
return state..done = true;
|
||||||
final fallback = '> ${details.quoteBody!}';
|
}
|
||||||
|
|
||||||
stanza
|
/// Send an unawaitable message to [to]. [extensions] is a typed map that contains
|
||||||
..addChild(
|
/// data for building the message.
|
||||||
XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
|
Future<void> sendMessage(
|
||||||
)
|
JID to,
|
||||||
..addChild(
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
XMLNode.xmlns(
|
) async {
|
||||||
tag: 'reply',
|
await getAttributes().sendStanza(
|
||||||
xmlns: replyXmlns,
|
StanzaDetails(
|
||||||
attributes: {
|
Stanza.message(
|
||||||
'to': details.quoteFrom!,
|
to: to.toString(),
|
||||||
'id': details.quoteId!
|
id: extensions.get<MessageIdData>()?.id,
|
||||||
},
|
type: 'chat',
|
||||||
),
|
children: _messageSendingCallbacks
|
||||||
)
|
.map((c) => c(extensions))
|
||||||
..addChild(
|
.flattened
|
||||||
XMLNode.xmlns(
|
.toList(),
|
||||||
tag: 'fallback',
|
|
||||||
xmlns: fallbackXmlns,
|
|
||||||
attributes: {
|
|
||||||
'for': replyXmlns
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode(
|
|
||||||
tag: 'body',
|
|
||||||
attributes: <String, String>{
|
|
||||||
'start': '0',
|
|
||||||
'end': '${fallback.length}'
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var body = details.body;
|
|
||||||
if (details.sfs != null) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode(tag: 'body', text: body),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.requestDeliveryReceipt) {
|
|
||||||
stanza.addChild(makeMessageDeliveryRequest());
|
|
||||||
}
|
|
||||||
if (details.requestChatMarkers) {
|
|
||||||
stanza.addChild(makeChatMarkerMarkable());
|
|
||||||
}
|
|
||||||
if (details.originId != null) {
|
|
||||||
stanza.addChild(makeOriginIdElement(details.originId!));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.sfs != null) {
|
|
||||||
stanza.addChild(details.sfs!.toXML());
|
|
||||||
|
|
||||||
final source = details.sfs!.sources.first;
|
|
||||||
if (source is StatelessFileSharingUrlSource) {
|
|
||||||
// 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(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.funReplacement != null) {
|
final data = extensions.get<MessageBodyData>();
|
||||||
stanza.addChild(
|
return data != null ? [data.toXML()] : [];
|
||||||
XMLNode.xmlns(
|
}
|
||||||
tag: 'replaces',
|
|
||||||
xmlns: fileUploadNotificationXmlns,
|
|
||||||
attributes: <String, String>{
|
|
||||||
'id': details.funReplacement!,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttributes().sendStanza(stanza, awaitable: false);
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ const fullStanzaXmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas';
|
|||||||
// RFC 6121
|
// RFC 6121
|
||||||
const rosterXmlns = 'jabber:iq:roster';
|
const rosterXmlns = 'jabber:iq:roster';
|
||||||
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
|
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
|
||||||
|
const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
|
||||||
|
|
||||||
// XEP-0004
|
// XEP-0004
|
||||||
const dataFormsXmlns = 'jabber:x:data';
|
const dataFormsXmlns = 'jabber:x:data';
|
||||||
@@ -28,9 +29,11 @@ const vCardTempUpdate = 'vcard-temp:x:update';
|
|||||||
const pubsubXmlns = 'http://jabber.org/protocol/pubsub';
|
const pubsubXmlns = 'http://jabber.org/protocol/pubsub';
|
||||||
const pubsubEventXmlns = 'http://jabber.org/protocol/pubsub#event';
|
const pubsubEventXmlns = 'http://jabber.org/protocol/pubsub#event';
|
||||||
const pubsubOwnerXmlns = 'http://jabber.org/protocol/pubsub#owner';
|
const pubsubOwnerXmlns = 'http://jabber.org/protocol/pubsub#owner';
|
||||||
const pubsubPublishOptionsXmlns = 'http://jabber.org/protocol/pubsub#publish-options';
|
const pubsubPublishOptionsXmlns =
|
||||||
|
'http://jabber.org/protocol/pubsub#publish-options';
|
||||||
const pubsubNodeConfigMax = 'http://jabber.org/protocol/pubsub#config-node-max';
|
const pubsubNodeConfigMax = 'http://jabber.org/protocol/pubsub#config-node-max';
|
||||||
const pubsubNodeConfigMultiItems = 'http://jabber.org/protocol/pubsub#multi-items';
|
const pubsubNodeConfigMultiItems =
|
||||||
|
'http://jabber.org/protocol/pubsub#multi-items';
|
||||||
|
|
||||||
// XEP-0066
|
// XEP-0066
|
||||||
const oobDataXmlns = 'jabber:x:oob';
|
const oobDataXmlns = 'jabber:x:oob';
|
||||||
@@ -42,6 +45,9 @@ const userAvatarMetadataXmlns = 'urn:xmpp:avatar:metadata';
|
|||||||
// XEP-0085
|
// XEP-0085
|
||||||
const chatStateXmlns = 'http://jabber.org/protocol/chatstates';
|
const chatStateXmlns = 'http://jabber.org/protocol/chatstates';
|
||||||
|
|
||||||
|
// XEP-0114
|
||||||
|
const componentAcceptXmlns = 'jabber:component:accept';
|
||||||
|
|
||||||
// XEP-0115
|
// XEP-0115
|
||||||
const capsXmlns = 'http://jabber.org/protocol/caps';
|
const capsXmlns = 'http://jabber.org/protocol/caps';
|
||||||
|
|
||||||
@@ -69,12 +75,9 @@ const forwardedXmlns = 'urn:xmpp:forward:0';
|
|||||||
// XEP-0300
|
// XEP-0300
|
||||||
const hashXmlns = 'urn:xmpp:hashes:2';
|
const hashXmlns = 'urn:xmpp:hashes:2';
|
||||||
const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names';
|
const hashFunctionNameBaseXmlns = 'urn:xmpp:hash-function-text-names';
|
||||||
const hashSha256 = 'sha-256';
|
|
||||||
const hashSha512 = 'sha-512';
|
// XEP-0308
|
||||||
const hashSha3256 = 'sha3-256';
|
const lmcXmlns = 'urn:xmpp:message-correct:0';
|
||||||
const hashSha3512 = 'sha3-512';
|
|
||||||
const hashBlake2b256 = 'blake2b-256';
|
|
||||||
const hashBlake2b512 = 'blake2b-512';
|
|
||||||
|
|
||||||
// XEP-0333
|
// XEP-0333
|
||||||
const chatMarkersXmlns = 'urn:xmpp:chat-markers:0';
|
const chatMarkersXmlns = 'urn:xmpp:chat-markers:0';
|
||||||
@@ -94,7 +97,7 @@ const httpFileUploadXmlns = 'urn:xmpp:http:upload:0';
|
|||||||
// XEP-0372
|
// XEP-0372
|
||||||
const referenceXmlns = 'urn:xmpp:reference:0';
|
const referenceXmlns = 'urn:xmpp:reference:0';
|
||||||
|
|
||||||
// XEP-380
|
// XEP-0380
|
||||||
const emeXmlns = 'urn:xmpp:eme:0';
|
const emeXmlns = 'urn:xmpp:eme:0';
|
||||||
const emeOtr = 'urn:xmpp:otr:0';
|
const emeOtr = 'urn:xmpp:otr:0';
|
||||||
const emeLegacyOpenPGP = 'jabber:x:encrypted';
|
const emeLegacyOpenPGP = 'jabber:x:encrypted';
|
||||||
@@ -111,9 +114,27 @@ const omemoBundlesXmlns = 'urn:xmpp:omemo:2:bundles';
|
|||||||
// XEP-0385
|
// XEP-0385
|
||||||
const simsXmlns = 'urn:xmpp:sims:1';
|
const simsXmlns = 'urn:xmpp:sims:1';
|
||||||
|
|
||||||
|
// XEP-0386
|
||||||
|
const bind2Xmlns = 'urn:xmpp:bind:0';
|
||||||
|
|
||||||
|
// XEP-0388
|
||||||
|
const sasl2Xmlns = 'urn:xmpp:sasl:2';
|
||||||
|
|
||||||
// XEP-0420
|
// XEP-0420
|
||||||
const sceXmlns = 'urn:xmpp:sce:1';
|
const sceXmlns = 'urn:xmpp:sce:1';
|
||||||
|
|
||||||
|
// XEP-0422
|
||||||
|
const fasteningXmlns = 'urn:xmpp:fasten:0';
|
||||||
|
|
||||||
|
// XEP-0424
|
||||||
|
const messageRetractionXmlns = 'urn:xmpp:message-retract:0';
|
||||||
|
|
||||||
|
// XEP-0428
|
||||||
|
const fallbackIndicationXmlns = 'urn:xmpp:fallback:0';
|
||||||
|
|
||||||
|
// XEP-0444
|
||||||
|
const messageReactionsXmlns = 'urn:xmpp:reactions:0';
|
||||||
|
|
||||||
// XEP-0446
|
// XEP-0446
|
||||||
const fileMetadataXmlns = 'urn:xmpp:file:metadata:0';
|
const fileMetadataXmlns = 'urn:xmpp:file:metadata:0';
|
||||||
|
|
||||||
@@ -122,13 +143,21 @@ const sfsXmlns = 'urn:xmpp:sfs:0';
|
|||||||
|
|
||||||
// XEP-0448
|
// XEP-0448
|
||||||
const sfsEncryptionXmlns = 'urn:xmpp:esfs:0';
|
const sfsEncryptionXmlns = 'urn:xmpp:esfs:0';
|
||||||
const sfsEncryptionAes128GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-128-gcm-nopadding:0';
|
const sfsEncryptionAes128GcmNoPaddingXmlns =
|
||||||
const sfsEncryptionAes256GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-256-gcm-nopadding:0';
|
'urn:xmpp:ciphers:aes-128-gcm-nopadding:0';
|
||||||
|
const sfsEncryptionAes256GcmNoPaddingXmlns =
|
||||||
|
'urn:xmpp:ciphers:aes-256-gcm-nopadding:0';
|
||||||
const sfsEncryptionAes256CbcPkcs7Xmlns = 'urn:xmpp:ciphers:aes-256-cbc-pkcs7:0';
|
const sfsEncryptionAes256CbcPkcs7Xmlns = 'urn:xmpp:ciphers:aes-256-cbc-pkcs7:0';
|
||||||
|
|
||||||
|
// XEP-0449
|
||||||
|
const stickersXmlns = 'urn:xmpp:stickers:0';
|
||||||
|
|
||||||
// XEP-0461
|
// XEP-0461
|
||||||
const replyXmlns = 'urn:xmpp:reply:0';
|
const replyXmlns = 'urn:xmpp:reply:0';
|
||||||
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
||||||
|
|
||||||
// ???
|
// ???
|
||||||
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
||||||
|
|
||||||
|
// XEP-XXXX
|
||||||
|
const fastXmlns = 'urn:xmpp:fast:0';
|
||||||
Whitespace-only changes.
@@ -7,3 +7,8 @@ const rosterNegotiator = 'im.moxxmpp.core.roster';
|
|||||||
const resourceBindingNegotiator = 'im.moxxmpp.core.resource';
|
const resourceBindingNegotiator = 'im.moxxmpp.core.resource';
|
||||||
const streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
|
const streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
|
||||||
const startTlsNegotiator = 'im.moxxmpp.core.starttls';
|
const startTlsNegotiator = 'im.moxxmpp.core.starttls';
|
||||||
|
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,10 +1,14 @@
|
|||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/settings.dart';
|
import 'package:moxxmpp/src/settings.dart';
|
||||||
import 'package:moxxmpp/src/socket.dart';
|
import 'package:moxxmpp/src/socket.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
|
|
||||||
/// The state a negotiator is currently in
|
/// The state a negotiator is currently in
|
||||||
enum NegotiatorState {
|
enum NegotiatorState {
|
||||||
@@ -14,17 +18,18 @@ enum NegotiatorState {
|
|||||||
done,
|
done,
|
||||||
// Cancel the current attempt but we are not done
|
// Cancel the current attempt but we are not done
|
||||||
retryLater,
|
retryLater,
|
||||||
// The negotiator is in an error state
|
|
||||||
error,
|
|
||||||
// Skip the rest of the negotiation and assume the stream ready. Only use this when
|
// Skip the rest of the negotiation and assume the stream ready. Only use this when
|
||||||
// using stream restoration XEPs, like Stream Management.
|
// using stream restoration XEPs, like Stream Management.
|
||||||
skipRest,
|
skipRest,
|
||||||
}
|
}
|
||||||
|
|
||||||
class NegotiatorAttributes {
|
/// A base class for all errors that may occur during feature negotiation
|
||||||
|
abstract class NegotiatorError extends XmppError {}
|
||||||
|
|
||||||
|
class NegotiatorAttributes {
|
||||||
const NegotiatorAttributes(
|
const NegotiatorAttributes(
|
||||||
this.sendNonza,
|
this.sendNonza,
|
||||||
|
this.getConnection,
|
||||||
this.getConnectionSettings,
|
this.getConnectionSettings,
|
||||||
this.sendEvent,
|
this.sendEvent,
|
||||||
this.getNegotiatorById,
|
this.getNegotiatorById,
|
||||||
@@ -32,29 +37,59 @@ class NegotiatorAttributes {
|
|||||||
this.getFullJID,
|
this.getFullJID,
|
||||||
this.getSocket,
|
this.getSocket,
|
||||||
this.isAuthenticated,
|
this.isAuthenticated,
|
||||||
|
this.setAuthenticated,
|
||||||
|
this.setResource,
|
||||||
|
this.removeNegotiatingFeature,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Sends the nonza nonza and optionally redacts it in logs if redact is not null.
|
/// Sends the nonza nonza and optionally redacts it in logs if redact is not null.
|
||||||
final void Function(XMLNode nonza, {String? redact}) sendNonza;
|
final void Function(XMLNode nonza) sendNonza;
|
||||||
|
|
||||||
/// Returns the connection settings.
|
/// Returns the connection settings.
|
||||||
final ConnectionSettings Function() getConnectionSettings;
|
final ConnectionSettings Function() getConnectionSettings;
|
||||||
/// Send an event event to the connection's event bus
|
|
||||||
|
/// Returns the connection object.
|
||||||
|
final XmppConnection Function() getConnection;
|
||||||
|
|
||||||
|
/// Send an event event to the connection's event bus.
|
||||||
final Future<void> Function(XmppEvent event) sendEvent;
|
final Future<void> Function(XmppEvent event) sendEvent;
|
||||||
|
|
||||||
/// Returns the negotiator with id id of the connection or null.
|
/// Returns the negotiator with id id of the connection or null.
|
||||||
final T? Function<T extends XmppFeatureNegotiatorBase>(String) getNegotiatorById;
|
final T? Function<T extends XmppFeatureNegotiatorBase>(String)
|
||||||
|
getNegotiatorById;
|
||||||
|
|
||||||
/// Returns the manager with id id of the connection or null.
|
/// Returns the manager with id id of the connection or null.
|
||||||
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
||||||
|
|
||||||
/// Returns the full JID of the current account
|
/// Returns the full JID of the current account
|
||||||
final JID Function() getFullJID;
|
final JID Function() getFullJID;
|
||||||
|
|
||||||
/// Returns the socket the negotiator is attached to
|
/// Returns the socket the negotiator is attached to
|
||||||
final BaseSocketWrapper Function() getSocket;
|
final BaseSocketWrapper Function() getSocket;
|
||||||
|
|
||||||
/// Returns true if the stream is authenticated. Returns false if not.
|
/// Returns true if the stream is authenticated. Returns false if not.
|
||||||
final bool Function() isAuthenticated;
|
final bool Function() isAuthenticated;
|
||||||
|
|
||||||
|
/// Sets the resource of the connection. If triggerEvent is true, then a
|
||||||
|
/// [ResourceBoundEvent] is triggered.
|
||||||
|
final void Function(String, {bool triggerEvent}) setResource;
|
||||||
|
|
||||||
|
/// Sets the authentication state of the connection to true.
|
||||||
|
final void Function() setAuthenticated;
|
||||||
|
|
||||||
|
/// Remove a stream feature from our internal cache. This is useful for when you
|
||||||
|
/// negotiated a feature for another negotiator, like SASL2.
|
||||||
|
final void Function(String) removeNegotiatingFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class XmppFeatureNegotiatorBase {
|
abstract class XmppFeatureNegotiatorBase {
|
||||||
|
XmppFeatureNegotiatorBase(
|
||||||
|
this.priority,
|
||||||
|
this.sendStreamHeaderWhenDone,
|
||||||
|
this.negotiatingXmlns,
|
||||||
|
this.id,
|
||||||
|
) : state = NegotiatorState.ready;
|
||||||
|
|
||||||
XmppFeatureNegotiatorBase(this.priority, this.sendStreamHeaderWhenDone, this.negotiatingXmlns, this.id)
|
|
||||||
: state = NegotiatorState.ready;
|
|
||||||
/// The priority regarding other negotiators. The higher, the earlier will the
|
/// The priority regarding other negotiators. The higher, the earlier will the
|
||||||
/// negotiator be used
|
/// negotiator be used
|
||||||
final int priority;
|
final int priority;
|
||||||
@@ -83,11 +118,15 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
return firstWhereOrNull(
|
return firstWhereOrNull(
|
||||||
features,
|
features,
|
||||||
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
(XMLNode feature) => feature.attributes['xmlns'] == negotiatingXmlns,
|
||||||
) != null;
|
) !=
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when an event is triggered in the [XmppConnection].
|
||||||
|
Future<void> onXmppEvent(XmppEvent event) async {}
|
||||||
|
|
||||||
/// Called with the currently received nonza [nonza] when the negotiator is active.
|
/// Called with the currently received nonza [nonza] when the negotiator is active.
|
||||||
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
||||||
/// the <stream:features /> nonza.
|
/// the <stream:features /> nonza.
|
||||||
@@ -97,12 +136,17 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
/// must switch some internal state to prevent getting matched immediately again.
|
/// must switch some internal state to prevent getting matched immediately again.
|
||||||
/// If ready is returned, then the negotiator indicates that it is not done with
|
/// If ready is returned, then the negotiator indicates that it is not done with
|
||||||
/// negotiation.
|
/// negotiation.
|
||||||
Future<void> negotiate(XMLNode nonza);
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza);
|
||||||
|
|
||||||
/// Reset the negotiator to a state that negotation can happen again.
|
/// Reset the negotiator to a state that negotation can happen again.
|
||||||
void reset() {
|
void reset() {
|
||||||
state = NegotiatorState.ready;
|
state = NegotiatorState.ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@protected
|
||||||
NegotiatorAttributes get attributes => _attributes;
|
NegotiatorAttributes get attributes => _attributes;
|
||||||
|
|
||||||
|
/// Run after all negotiators are registered. Useful for registering callbacks against
|
||||||
|
/// other negotiators. By default this function does nothing.
|
||||||
|
Future<void> postRegisterCallback() async {}
|
||||||
}
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/events.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/xeps/xep_0198/xep_0198.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
|
||||||
|
|
||||||
ResourceBindingNegotiator() : _requestSent = false, super(0, false, bindXmlns, resourceBindingNegotiator);
|
|
||||||
bool _requestSent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager);
|
|
||||||
if (sm != null) {
|
|
||||||
return super.matchesFeature(features) && !sm.streamResumed && attributes.isAuthenticated();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.matchesFeature(features) && attributes.isAuthenticated();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> negotiate(XMLNode nonza) async {
|
|
||||||
if (!_requestSent) {
|
|
||||||
final stanza = XMLNode.xmlns(
|
|
||||||
tag: 'iq',
|
|
||||||
xmlns: stanzaXmlns,
|
|
||||||
attributes: {
|
|
||||||
'type': 'set',
|
|
||||||
'id': const Uuid().v4(),
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'bind',
|
|
||||||
xmlns: bindXmlns,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
_requestSent = true;
|
|
||||||
attributes.sendNonza(stanza);
|
|
||||||
} else {
|
|
||||||
if (nonza.tag != 'iq' || nonza.attributes['type'] != 'result') {
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final bind = nonza.firstTag('bind')!;
|
|
||||||
final jid = bind.firstTag('jid')!;
|
|
||||||
final resource = jid.innerText().split('/')[1];
|
|
||||||
|
|
||||||
await attributes.sendEvent(ResourceBindingSuccessEvent(resource: resource));
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_requestSent = false;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
enum ParserState {
|
|
||||||
variableName,
|
|
||||||
variableValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a string like "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL" into
|
|
||||||
/// { "n": "user", "r": "fyko+d2lbbFgONRv9qkxdawL"}.
|
|
||||||
Map<String, String> parseKeyValue(String keyValueString) {
|
|
||||||
var state = ParserState.variableName;
|
|
||||||
var name = '';
|
|
||||||
var value = '';
|
|
||||||
final values = <String, String>{};
|
|
||||||
|
|
||||||
for (var i = 0; i < keyValueString.length; i++) {
|
|
||||||
final char = keyValueString[i];
|
|
||||||
switch (state) {
|
|
||||||
case ParserState.variableName: {
|
|
||||||
if (char == '=') {
|
|
||||||
state = ParserState.variableValue;
|
|
||||||
} else if (char == ',') {
|
|
||||||
name = '';
|
|
||||||
} else {
|
|
||||||
name += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ParserState.variableValue: {
|
|
||||||
if (char == ',' || i == keyValueString.length - 1) {
|
|
||||||
if (char != ',') {
|
|
||||||
value += char;
|
|
||||||
}
|
|
||||||
|
|
||||||
values[name] = value;
|
|
||||||
value = '';
|
|
||||||
name = '';
|
|
||||||
state = ParserState.variableName;
|
|
||||||
} else {
|
|
||||||
value += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
|
|
||||||
class SaslAuthNonza extends XMLNode {
|
|
||||||
SaslAuthNonza(String mechanism, String body) : super(
|
|
||||||
tag: 'auth',
|
|
||||||
attributes: <String, String>{
|
|
||||||
'xmlns': saslXmlns,
|
|
||||||
'mechanism': mechanism ,
|
|
||||||
},
|
|
||||||
text: body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:logging/logging.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/negotiators/sasl/negotiator.dart';
|
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/nonza.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
|
|
||||||
class SaslPlainAuthNonza extends SaslAuthNonza {
|
|
||||||
SaslPlainAuthNonza(String username, String password) : super(
|
|
||||||
'PLAIN', base64.encode(utf8.encode('\u0000$username\u0000$password')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SaslPlainNegotiator extends SaslNegotiator {
|
|
||||||
|
|
||||||
SaslPlainNegotiator()
|
|
||||||
: _authSent = false,
|
|
||||||
_log = Logger('SaslPlainNegotiator'),
|
|
||||||
super(0, saslPlainNegotiator, 'PLAIN');
|
|
||||||
bool _authSent;
|
|
||||||
|
|
||||||
final Logger _log;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
|
||||||
if (!attributes.getConnectionSettings().allowPlainAuth) return false;
|
|
||||||
|
|
||||||
if (super.matchesFeature(features)) {
|
|
||||||
if (!attributes.getSocket().isSecure()) {
|
|
||||||
_log.warning('Refusing to match SASL feature due to unsecured connection');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> negotiate(XMLNode nonza) async {
|
|
||||||
if (!_authSent) {
|
|
||||||
final settings = attributes.getConnectionSettings();
|
|
||||||
attributes.sendNonza(
|
|
||||||
SaslPlainAuthNonza(settings.jid.local, settings.password),
|
|
||||||
redact: SaslPlainAuthNonza('******', '******').toXml(),
|
|
||||||
);
|
|
||||||
_authSent = true;
|
|
||||||
} else {
|
|
||||||
final tag = nonza.tag;
|
|
||||||
if (tag == 'success') {
|
|
||||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
} else {
|
|
||||||
// We assume it's a <failure/>
|
|
||||||
final error = nonza.children.first.tag;
|
|
||||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_authSent = false;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math' show Random;
|
|
||||||
|
|
||||||
import 'package:cryptography/cryptography.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:moxxmpp/src/events.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/negotiators/sasl/kv.dart';
|
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart';
|
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/nonza.dart';
|
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
|
||||||
import 'package:random_string/random_string.dart';
|
|
||||||
import 'package:saslprep/saslprep.dart';
|
|
||||||
|
|
||||||
// NOTE: Inspired by https://github.com/vukoye/xmpp_dart/blob/3b1a0588562b9e591488c99d834088391840911d/lib/src/features/sasl/ScramSaslHandler.dart
|
|
||||||
|
|
||||||
enum ScramHashType {
|
|
||||||
sha1,
|
|
||||||
sha256,
|
|
||||||
sha512
|
|
||||||
}
|
|
||||||
|
|
||||||
HashAlgorithm hashFromType(ScramHashType type) {
|
|
||||||
switch (type) {
|
|
||||||
case ScramHashType.sha1: return Sha1();
|
|
||||||
case ScramHashType.sha256: return Sha256();
|
|
||||||
case ScramHashType.sha512: return Sha512();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scramSha1Mechanism = 'SCRAM-SHA-1';
|
|
||||||
const scramSha256Mechanism = 'SCRAM-SHA-256';
|
|
||||||
const scramSha512Mechanism = 'SCRAM-SHA-512';
|
|
||||||
|
|
||||||
String mechanismNameFromType(ScramHashType type) {
|
|
||||||
switch (type) {
|
|
||||||
case ScramHashType.sha1: return scramSha1Mechanism;
|
|
||||||
case ScramHashType.sha256: return scramSha256Mechanism;
|
|
||||||
case ScramHashType.sha512: return scramSha512Mechanism;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String namespaceFromType(ScramHashType type) {
|
|
||||||
switch (type) {
|
|
||||||
case ScramHashType.sha1: return saslScramSha1Negotiator;
|
|
||||||
case ScramHashType.sha256: return saslScramSha256Negotiator;
|
|
||||||
case ScramHashType.sha512: return saslScramSha512Negotiator;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SaslScramAuthNonza extends SaslAuthNonza {
|
|
||||||
// This subclassing makes less sense here, but this is since the auth nonza here
|
|
||||||
// requires knowledge of the inner state of the Negotiator.
|
|
||||||
SaslScramAuthNonza({ required ScramHashType type, required String body }) : super(
|
|
||||||
mechanismNameFromType(type), body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SaslScramResponseNonza extends XMLNode {
|
|
||||||
SaslScramResponseNonza({ required String body }) : super(
|
|
||||||
tag: 'response',
|
|
||||||
attributes: <String, String>{
|
|
||||||
'xmlns': saslXmlns,
|
|
||||||
},
|
|
||||||
text: body,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ScramState {
|
|
||||||
preSent,
|
|
||||||
initialMessageSent,
|
|
||||||
challengeResponseSent,
|
|
||||||
error
|
|
||||||
}
|
|
||||||
|
|
||||||
const gs2Header = 'n,,';
|
|
||||||
|
|
||||||
class SaslScramNegotiator extends SaslNegotiator {
|
|
||||||
|
|
||||||
// NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing
|
|
||||||
SaslScramNegotiator(
|
|
||||||
int priority,
|
|
||||||
this.initialMessageNoGS2,
|
|
||||||
this.clientNonce,
|
|
||||||
this.hashType,
|
|
||||||
) :
|
|
||||||
_hash = hashFromType(hashType),
|
|
||||||
_serverSignature = '',
|
|
||||||
_scramState = ScramState.preSent,
|
|
||||||
_log = Logger('SaslScramNegotiator(${mechanismNameFromType(hashType)})'),
|
|
||||||
super(priority, namespaceFromType(hashType), mechanismNameFromType(hashType));
|
|
||||||
String? clientNonce;
|
|
||||||
String initialMessageNoGS2;
|
|
||||||
final ScramHashType hashType;
|
|
||||||
final HashAlgorithm _hash;
|
|
||||||
String _serverSignature;
|
|
||||||
|
|
||||||
// The internal state for performing the negotiation
|
|
||||||
ScramState _scramState;
|
|
||||||
|
|
||||||
final Logger _log;
|
|
||||||
|
|
||||||
Future<List<int>> calculateSaltedPassword(String salt, int iterations) async {
|
|
||||||
final pbkdf2 = Pbkdf2(
|
|
||||||
macAlgorithm: Hmac(_hash),
|
|
||||||
iterations: iterations,
|
|
||||||
bits: 160, // NOTE: RFC says 20 octets => 20 octets * 8 bits/octet
|
|
||||||
);
|
|
||||||
|
|
||||||
final saltedPasswordRaw = await pbkdf2.deriveKey(
|
|
||||||
secretKey: SecretKey(
|
|
||||||
utf8.encode(Saslprep.saslprep(attributes.getConnectionSettings().password)),
|
|
||||||
),
|
|
||||||
nonce: base64.decode(salt),
|
|
||||||
);
|
|
||||||
return saltedPasswordRaw.extractBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> calculateClientKey(List<int> saltedPassword) async {
|
|
||||||
return (await Hmac(_hash).calculateMac(
|
|
||||||
utf8.encode('Client Key'), secretKey: SecretKey(saltedPassword),
|
|
||||||
)).bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> calculateClientSignature(String authMessage, List<int> storedKey) async {
|
|
||||||
return (await Hmac(_hash).calculateMac(
|
|
||||||
utf8.encode(authMessage),
|
|
||||||
secretKey: SecretKey(storedKey),
|
|
||||||
)).bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> calculateServerKey(List<int> saltedPassword) async {
|
|
||||||
return (await Hmac(_hash).calculateMac(
|
|
||||||
utf8.encode('Server Key'),
|
|
||||||
secretKey: SecretKey(saltedPassword),
|
|
||||||
)).bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<int>> calculateServerSignature(String authMessage, List<int> serverKey) async {
|
|
||||||
return (await Hmac(_hash).calculateMac(
|
|
||||||
utf8.encode(authMessage),
|
|
||||||
secretKey: SecretKey(serverKey),
|
|
||||||
)).bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> calculateClientProof(List<int> clientKey, List<int> clientSignature) {
|
|
||||||
final clientProof = List<int>.filled(clientKey.length, 0);
|
|
||||||
for (var i = 0; i < clientKey.length; i++) {
|
|
||||||
clientProof[i] = clientKey[i] ^ clientSignature[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientProof;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> calculateChallengeResponse(String base64Challenge) async {
|
|
||||||
final challengeString = utf8.decode(base64.decode(base64Challenge));
|
|
||||||
final challenge = parseKeyValue(challengeString);
|
|
||||||
final clientFinalMessageBare = 'c=biws,r=${challenge['r']!}';
|
|
||||||
|
|
||||||
final saltedPassword = await calculateSaltedPassword(challenge['s']!, int.parse(challenge['i']!));
|
|
||||||
final clientKey = await calculateClientKey(saltedPassword);
|
|
||||||
final storedKey = (await _hash.hash(clientKey)).bytes;
|
|
||||||
final authMessage = '$initialMessageNoGS2,$challengeString,$clientFinalMessageBare';
|
|
||||||
final clientSignature = await calculateClientSignature(authMessage, storedKey);
|
|
||||||
final clientProof = calculateClientProof(clientKey, clientSignature);
|
|
||||||
final serverKey = await calculateServerKey(saltedPassword);
|
|
||||||
_serverSignature = base64.encode(await calculateServerSignature(authMessage, serverKey));
|
|
||||||
|
|
||||||
return '$clientFinalMessageBare,p=${base64.encode(clientProof)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
|
||||||
if (super.matchesFeature(features)) {
|
|
||||||
if (!attributes.getSocket().isSecure()) {
|
|
||||||
_log.warning('Refusing to match SASL feature due to unsecured connection');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> negotiate(XMLNode nonza) async {
|
|
||||||
switch (_scramState) {
|
|
||||||
case ScramState.preSent:
|
|
||||||
if (clientNonce == null || clientNonce == '') {
|
|
||||||
clientNonce = randomAlphaNumeric(40, provider: CoreRandomProvider.from(Random.secure()));
|
|
||||||
}
|
|
||||||
|
|
||||||
initialMessageNoGS2 = 'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce';
|
|
||||||
|
|
||||||
_scramState = ScramState.initialMessageSent;
|
|
||||||
attributes.sendNonza(
|
|
||||||
SaslScramAuthNonza(body: base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)), type: hashType),
|
|
||||||
redact: SaslScramAuthNonza(body: '******', type: hashType).toXml(),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case ScramState.initialMessageSent:
|
|
||||||
if (nonza.tag != 'challenge') {
|
|
||||||
final error = nonza.children.first.tag;
|
|
||||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
_scramState = ScramState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final challengeBase64 = nonza.innerText();
|
|
||||||
final response = await calculateChallengeResponse(challengeBase64);
|
|
||||||
final responseBase64 = base64.encode(utf8.encode(response));
|
|
||||||
_scramState = ScramState.challengeResponseSent;
|
|
||||||
attributes.sendNonza(
|
|
||||||
SaslScramResponseNonza(body: responseBase64),
|
|
||||||
redact: SaslScramResponseNonza(body: '******').toXml(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
case ScramState.challengeResponseSent:
|
|
||||||
if (nonza.tag != 'success') {
|
|
||||||
// We assume it's a <failure />
|
|
||||||
final error = nonza.children.first.tag;
|
|
||||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
_scramState = ScramState.error;
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: This assumes that the string is always "v=..." and contains no other parameters
|
|
||||||
final signature = parseKeyValue(utf8.decode(base64.decode(nonza.innerText())));
|
|
||||||
if (signature['v']! != _serverSignature) {
|
|
||||||
// TODO(Unknown): Notify of a signature mismatch
|
|
||||||
//final error = nonza.children.first.tag;
|
|
||||||
//attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
_scramState = ScramState.error;
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
return;
|
|
||||||
case ScramState.error:
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_scramState = ScramState.preSent;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import 'package:logging/logging.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';
|
|
||||||
|
|
||||||
enum _StartTlsState {
|
|
||||||
ready,
|
|
||||||
requested
|
|
||||||
}
|
|
||||||
|
|
||||||
class StartTLSNonza extends XMLNode {
|
|
||||||
StartTLSNonza() : super.xmlns(
|
|
||||||
tag: 'starttls',
|
|
||||||
xmlns: startTlsXmlns,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class StartTlsNegotiator extends XmppFeatureNegotiatorBase {
|
|
||||||
|
|
||||||
StartTlsNegotiator()
|
|
||||||
: _state = _StartTlsState.ready,
|
|
||||||
_log = Logger('StartTlsNegotiator'),
|
|
||||||
super(10, true, startTlsXmlns, startTlsNegotiator);
|
|
||||||
_StartTlsState _state;
|
|
||||||
|
|
||||||
final Logger _log;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> negotiate(XMLNode nonza) async {
|
|
||||||
switch (_state) {
|
|
||||||
case _StartTlsState.ready:
|
|
||||||
_log.fine('StartTLS is available. Performing StartTLS upgrade...');
|
|
||||||
_state = _StartTlsState.requested;
|
|
||||||
attributes.sendNonza(StartTLSNonza());
|
|
||||||
break;
|
|
||||||
case _StartTlsState.requested:
|
|
||||||
if (nonza.tag != 'proceed' || nonza.attributes['xmlns'] != startTlsXmlns) {
|
|
||||||
_log.severe('Failed to perform StartTLS negotiation');
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.fine('Securing socket');
|
|
||||||
final result = await attributes.getSocket()
|
|
||||||
.secure(attributes.getConnectionSettings().jid.domain);
|
|
||||||
if (!result) {
|
|
||||||
_log.severe('Failed to secure stream');
|
|
||||||
state = NegotiatorState.error;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.fine('Stream is now TLS secured');
|
|
||||||
state = NegotiatorState.done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_state = _StartTlsState.ready;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
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, XMPPStreamObject> {
|
||||||
|
final StreamController<XMPPStreamObject> _streamController =
|
||||||
|
StreamController<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<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);
|
||||||
|
|
||||||
|
// Process the stream header separately.
|
||||||
|
for (final event in streamHeaderEvents) {
|
||||||
|
if (event is! XmlStartElementEvent) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.name != 'stream:stream') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamController.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) {
|
||||||
|
_streamController.add(
|
||||||
|
XMPPStreamElement(
|
||||||
|
XMLNode.fromXmlElement(node as XmlElement),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return _streamController.stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +1,100 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
|
/// This manager class is responsible to sending periodic pings, if required, using
|
||||||
|
/// either whitespaces or Stream Management. Keep in mind, that without
|
||||||
|
/// Stream Management, a stale connection cannot be detected.
|
||||||
class PingManager extends XmppManagerBase {
|
class PingManager extends XmppManagerBase {
|
||||||
@override
|
PingManager(this._pingDuration) : super(pingManager);
|
||||||
String getId() => pingManager;
|
|
||||||
|
|
||||||
@override
|
/// The time between pings, when connected.
|
||||||
String getName() => 'PingManager';
|
final Duration _pingDuration;
|
||||||
|
|
||||||
|
/// The actual timer.
|
||||||
|
Timer? _pingTimer;
|
||||||
|
final Lock _timerLock = Lock();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
void _logWarning() {
|
void _logWarning() {
|
||||||
logger.warning('Cannot send keepalives as SM is not available, the socket disallows whitespace pings and does not manage its own keepalives. Cannot guarantee that the connection survives.');
|
logger.warning(
|
||||||
|
'Cannot send keepalives as SM is not available, the socket disallows whitespace pings and does not manage its own keepalives. Cannot guarantee that the connection survives.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel a potentially scheduled ping timer. Can be overriden to cancel a custom timing mechanism.
|
||||||
|
/// By default, cancels a [Timer.periodic] that was set up prior.
|
||||||
|
@visibleForOverriding
|
||||||
|
Future<void> cancelPing() async {
|
||||||
|
await _timerLock.synchronized(() {
|
||||||
|
logger.finest('Cancelling timer');
|
||||||
|
_pingTimer?.cancel();
|
||||||
|
_pingTimer = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule a ping to be sent after a given amount of time. Can be overriden for custom timing mechanisms.
|
||||||
|
/// By default, uses a [Timer.periodic] timer to trigger a ping.
|
||||||
|
/// NOTE: This function is called whenever the connection is re-established. Custom
|
||||||
|
/// implementations should thus guard against multiple timers being started.
|
||||||
|
@visibleForOverriding
|
||||||
|
Future<void> schedulePing() async {
|
||||||
|
await _timerLock.synchronized(() {
|
||||||
|
logger.finest('Scheduling new timer? ${_pingTimer != null}');
|
||||||
|
|
||||||
|
_pingTimer ??= Timer.periodic(
|
||||||
|
_pingDuration,
|
||||||
|
_sendPing,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendPing(Timer _) async {
|
||||||
|
logger.finest('Attempting to send ping');
|
||||||
|
final attrs = getAttributes();
|
||||||
|
final socket = attrs.getSocket();
|
||||||
|
|
||||||
|
if (socket.managesKeepalives()) {
|
||||||
|
logger.finest('Not sending ping as the socket manages it.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final stream = attrs.getManagerById(smManager) as StreamManagementManager?;
|
||||||
|
if (stream != null) {
|
||||||
|
if (stream
|
||||||
|
.isStreamManagementEnabled() /*&& stream.getUnackedStanzaCount() > 0*/) {
|
||||||
|
logger.finest('Sending an ack ping as Stream Management is enabled');
|
||||||
|
stream.sendAckRequestPing();
|
||||||
|
} else if (attrs.getSocket().whitespacePingAllowed()) {
|
||||||
|
logger.finest(
|
||||||
|
'Sending a whitespace ping as Stream Management is not enabled',
|
||||||
|
);
|
||||||
|
attrs.getConnection().sendWhitespacePing();
|
||||||
|
} else {
|
||||||
|
_logWarning();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attrs.getSocket().whitespacePingAllowed()) {
|
||||||
|
attrs.getConnection().sendWhitespacePing();
|
||||||
|
} else {
|
||||||
|
_logWarning();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
if (event is SendPingEvent) {
|
if (event is ConnectionStateChangedEvent) {
|
||||||
logger.finest('Received ping event.');
|
if (event.connectionEstablished) {
|
||||||
final attrs = getAttributes();
|
await schedulePing();
|
||||||
final socket = attrs.getSocket();
|
|
||||||
|
|
||||||
if (socket.managesKeepalives()) {
|
|
||||||
logger.finest('Not sending ping as the socket manages it.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final stream = attrs.getManagerById(smManager) as StreamManagementManager?;
|
|
||||||
if (stream != null) {
|
|
||||||
if (stream.isStreamManagementEnabled() /*&& stream.getUnackedStanzaCount() > 0*/) {
|
|
||||||
logger.finest('Sending an ack ping as Stream Management is enabled');
|
|
||||||
stream.sendAckRequestPing();
|
|
||||||
} else if (attrs.getSocket().whitespacePingAllowed()) {
|
|
||||||
logger.finest('Sending a whitespace ping as Stream Management is not enabled');
|
|
||||||
attrs.getConnection().sendWhitespacePing();
|
|
||||||
} else {
|
|
||||||
_logWarning();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (attrs.getSocket().whitespacePingAllowed()) {
|
await cancelPing();
|
||||||
attrs.getConnection().sendWhitespacePing();
|
|
||||||
} else {
|
|
||||||
_logWarning();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'dart:async';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@@ -6,155 +6,237 @@ import 'package:moxxmpp/src/managers/data.dart';
|
|||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0115.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0414.dart';
|
|
||||||
|
|
||||||
|
/// A function that will be called when presence, outside of subscription request
|
||||||
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
|
/// presence.
|
||||||
|
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
||||||
|
|
||||||
|
/// A 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 {
|
class PresenceManager extends XmppManagerBase {
|
||||||
PresenceManager(this._capHashNode) : _capabilityHash = null, super();
|
PresenceManager() : super(presenceManager);
|
||||||
String? _capabilityHash;
|
|
||||||
final String _capHashNode;
|
|
||||||
|
|
||||||
String get capabilityHashNode => _capHashNode;
|
/// The list of pre-send callbacks.
|
||||||
|
final List<PresencePreSendCallback> _presenceCallbacks =
|
||||||
|
List.empty(growable: true);
|
||||||
|
|
||||||
@override
|
/// The priority of the presence handler. If a handler should run before this one,
|
||||||
String getId() => presenceManager;
|
/// which terminates processing, make sure the handler has a priority greater than
|
||||||
|
/// [presenceHandlerPriority].
|
||||||
@override
|
static int presenceHandlerPriority = -100;
|
||||||
String getName() => 'PresenceManager';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'presence',
|
stanzaTag: 'presence',
|
||||||
callback: _onPresence,
|
callback: _onPresence,
|
||||||
)
|
priority: presenceHandlerPriority,
|
||||||
];
|
)
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> getDiscoFeatures() => [ capsXmlns ];
|
List<String> getDiscoFeatures() => [capsXmlns];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
|
/// Register the pre-send callback [callback].
|
||||||
|
void registerPreSendCallback(PresencePreSendCallback callback) {
|
||||||
|
_presenceCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
|
if (event is StreamNegotiationsDoneEvent) {
|
||||||
|
// Send initial presence only when we have not resumed the stream
|
||||||
|
if (!event.resumed) {
|
||||||
|
await sendInitialPresence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onPresence(
|
||||||
|
Stanza presence,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
switch (presence.type) {
|
switch (presence.type) {
|
||||||
case 'subscribe':
|
case 'subscribe':
|
||||||
case 'subscribed': {
|
case 'subscribed':
|
||||||
attrs.sendEvent(
|
{
|
||||||
SubscriptionRequestReceivedEvent(from: JID.fromString(presence.from!)),
|
attrs.sendEvent(
|
||||||
);
|
SubscriptionRequestReceivedEvent(
|
||||||
return state.copyWith(done: true);
|
from: JID.fromString(presence.from!),
|
||||||
}
|
),
|
||||||
default: break;
|
);
|
||||||
|
return state..done = true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (presence.from != null) {
|
if (presence.from != null) {
|
||||||
logger.finest("Received presence from '${presence.from}'");
|
logger.finest("Received presence from '${presence.from}'");
|
||||||
|
|
||||||
getAttributes().sendEvent(PresenceReceivedEvent(JID.fromString(presence.from!), presence));
|
return state..done = true;
|
||||||
return state.copyWith(done: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the capability hash.
|
|
||||||
Future<String> getCapabilityHash() async {
|
|
||||||
final manager = getAttributes().getManagerById(discoManager)! as DiscoManager;
|
|
||||||
_capabilityHash ??= await calculateCapabilityHash(
|
|
||||||
DiscoInfo(
|
|
||||||
manager.getRegisteredDiscoFeatures(),
|
|
||||||
manager.getIdentities(),
|
|
||||||
[],
|
|
||||||
getAttributes().getFullJID(),
|
|
||||||
),
|
|
||||||
getHashByName('sha-1')!,
|
|
||||||
);
|
|
||||||
|
|
||||||
return _capabilityHash!;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends the initial presence to enable receiving messages.
|
/// Sends the initial presence to enable receiving messages.
|
||||||
Future<void> sendInitialPresence() async {
|
Future<void> sendInitialPresence() async {
|
||||||
|
final children = List<XMLNode>.from([
|
||||||
|
XMLNode(
|
||||||
|
tag: 'show',
|
||||||
|
text: 'chat',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (final callback in _presenceCallbacks) {
|
||||||
|
children.addAll(
|
||||||
|
await callback(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
attrs.sendNonza(
|
await attrs.sendStanza(
|
||||||
Stanza.presence(
|
StanzaDetails(
|
||||||
from: attrs.getFullJID().toString(),
|
Stanza.presence(
|
||||||
children: [
|
children: children,
|
||||||
XMLNode(
|
),
|
||||||
tag: 'show',
|
awaitable: false,
|
||||||
text: 'chat',
|
addId: false,
|
||||||
),
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'c',
|
|
||||||
xmlns: capsXmlns,
|
|
||||||
attributes: {
|
|
||||||
'hash': 'sha-1',
|
|
||||||
'node': _capHashNode,
|
|
||||||
'ver': await getCapabilityHash()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an unavailable presence with no 'to' attribute.
|
/// Send an unavailable presence with no 'to' attribute.
|
||||||
void sendUnavailablePresence() {
|
Future<void> sendUnavailablePresence() async {
|
||||||
getAttributes().sendStanza(
|
// Bypass the queue so that this get's sent immediately.
|
||||||
Stanza.presence(
|
// If we do it like this, we can also block the disconnection
|
||||||
type: 'unavailable',
|
// until we're actually ready.
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
type: 'unavailable',
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
bypassQueue: true,
|
||||||
|
excludeFromStreamManagement: true,
|
||||||
),
|
),
|
||||||
addFrom: StanzaFromType.full,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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].
|
/// Sends a subscription request to [to].
|
||||||
void sendSubscriptionRequest(String to) {
|
Future<void> requestSubscription(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
Stanza.presence(
|
StanzaDetails(
|
||||||
type: 'subscribe',
|
Stanza.presence(
|
||||||
to: to,
|
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].
|
/// Sends an unsubscription request to [to].
|
||||||
void sendUnsubscriptionRequest(String to) {
|
Future<void> unsubscribe(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
Stanza.presence(
|
StanzaDetails(
|
||||||
type: 'unsubscribe',
|
Stanza.presence(
|
||||||
to: to,
|
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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,37 +4,66 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
abstract class ReconnectionPolicy {
|
/// A callback function to be called when the connection to the server has been lost.
|
||||||
|
typedef ConnectionLostCallback = Future<void> Function();
|
||||||
|
|
||||||
ReconnectionPolicy()
|
/// A function that, when called, causes the XmppConnection to connect to the server, if
|
||||||
: _shouldAttemptReconnection = false,
|
/// another reconnection is not already running.
|
||||||
_isReconnecting = false,
|
typedef PerformReconnectFunction = Future<void> Function();
|
||||||
_isReconnectingLock = Lock();
|
|
||||||
|
abstract class ReconnectionPolicy {
|
||||||
/// Function provided by XmppConnection that allows the policy
|
/// Function provided by XmppConnection that allows the policy
|
||||||
/// to perform a reconnection.
|
/// to perform a reconnection.
|
||||||
Future<void> Function()? performReconnect;
|
PerformReconnectFunction? performReconnect;
|
||||||
/// Function provided by XmppConnection that allows the policy
|
|
||||||
/// to say that we lost the connection.
|
final Lock _lock = Lock();
|
||||||
void Function()? triggerConnectionLost;
|
|
||||||
/// Indicate if should try to reconnect.
|
|
||||||
bool _shouldAttemptReconnection;
|
|
||||||
/// Indicate if a reconnection attempt is currently running.
|
/// Indicate if a reconnection attempt is currently running.
|
||||||
bool _isReconnecting;
|
bool _isReconnecting = false;
|
||||||
/// And the corresponding lock
|
|
||||||
final Lock _isReconnectingLock;
|
/// Indicate if should try to reconnect.
|
||||||
|
bool _shouldAttemptReconnection = false;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Future<bool> canTryReconnecting() async =>
|
||||||
|
_lock.synchronized(() => !_isReconnecting);
|
||||||
|
|
||||||
|
@protected
|
||||||
|
Future<bool> getIsReconnecting() async =>
|
||||||
|
_lock.synchronized(() => _isReconnecting);
|
||||||
|
|
||||||
|
Future<void> _resetIsReconnecting() async {
|
||||||
|
await _lock.synchronized(() {
|
||||||
|
_isReconnecting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by XmppConnection to register the policy.
|
/// Called by XmppConnection to register the policy.
|
||||||
void register(Future<void> Function() performReconnect, void Function() triggerConnectionLost) {
|
void register(
|
||||||
|
PerformReconnectFunction performReconnect,
|
||||||
|
) {
|
||||||
this.performReconnect = performReconnect;
|
this.performReconnect = performReconnect;
|
||||||
this.triggerConnectionLost = triggerConnectionLost;
|
|
||||||
|
|
||||||
unawaited(reset());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In case the policy depends on some internal state, this state must be reset
|
/// In case the policy depends on some internal state, this state must be reset
|
||||||
/// to an initial state when reset is called. In case timers run, they must be
|
/// to an initial state when reset is called. In case timers run, they must be
|
||||||
/// terminated.
|
/// terminated.
|
||||||
Future<void> reset();
|
@mustCallSuper
|
||||||
|
Future<void> reset() async {
|
||||||
|
await _resetIsReconnecting();
|
||||||
|
}
|
||||||
|
|
||||||
|
@mustCallSuper
|
||||||
|
Future<bool> canTriggerFailure() async {
|
||||||
|
return _lock.synchronized(() {
|
||||||
|
if (_shouldAttemptReconnection && !_isReconnecting) {
|
||||||
|
_isReconnecting = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by the XmppConnection when the reconnection failed.
|
/// Called by the XmppConnection when the reconnection failed.
|
||||||
Future<void> onFailure() async {}
|
Future<void> onFailure() async {}
|
||||||
@@ -42,99 +71,112 @@ abstract class ReconnectionPolicy {
|
|||||||
/// Caled by the XmppConnection when the reconnection was successful.
|
/// Caled by the XmppConnection when the reconnection was successful.
|
||||||
Future<void> onSuccess();
|
Future<void> onSuccess();
|
||||||
|
|
||||||
bool get shouldReconnect => _shouldAttemptReconnection;
|
Future<bool> getShouldReconnect() async {
|
||||||
|
return _lock.synchronized(() => _shouldAttemptReconnection);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether a reconnection attempt should be made.
|
/// Set whether a reconnection attempt should be made.
|
||||||
void setShouldReconnect(bool value) {
|
Future<void> setShouldReconnect(bool value) async {
|
||||||
_shouldAttemptReconnection = value;
|
return _lock.synchronized(() => _shouldAttemptReconnection = value);
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the manager is currently triggering a reconnection. If not, returns
|
|
||||||
/// false.
|
|
||||||
Future<bool> isReconnectionRunning() async {
|
|
||||||
return _isReconnectingLock.synchronized(() => _isReconnecting);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the _isReconnecting state to [value].
|
|
||||||
@protected
|
|
||||||
Future<void> setIsReconnecting(bool value) async {
|
|
||||||
await _isReconnectingLock.synchronized(() async {
|
|
||||||
_isReconnecting = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
Future<bool> testAndSetIsReconnecting() async {
|
|
||||||
return _isReconnectingLock.synchronized(() {
|
|
||||||
if (_isReconnecting) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
_isReconnecting = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
|
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
|
||||||
/// for every failed attempt.
|
/// for every failed attempt.
|
||||||
class ExponentialBackoffReconnectionPolicy extends ReconnectionPolicy {
|
/// NOTE: This ReconnectionPolicy may be broken
|
||||||
|
class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||||
|
RandomBackoffReconnectionPolicy(
|
||||||
|
this._minBackoffTime,
|
||||||
|
this._maxBackoffTime,
|
||||||
|
) : assert(
|
||||||
|
_minBackoffTime < _maxBackoffTime,
|
||||||
|
'_minBackoffTime must be smaller than _maxBackoffTime',
|
||||||
|
),
|
||||||
|
super();
|
||||||
|
|
||||||
ExponentialBackoffReconnectionPolicy()
|
/// The maximum time in seconds that a backoff should be.
|
||||||
: _counter = 0,
|
final int _maxBackoffTime;
|
||||||
_log = Logger('ExponentialBackoffReconnectionPolicy'),
|
|
||||||
super();
|
/// The minimum time in seconds that a backoff should be.
|
||||||
int _counter;
|
final int _minBackoffTime;
|
||||||
|
|
||||||
|
/// Backoff timer.
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
final Logger _log;
|
|
||||||
|
/// Logger.
|
||||||
|
final Logger _log = Logger('RandomBackoffReconnectionPolicy');
|
||||||
|
|
||||||
|
final Lock _timerLock = Lock();
|
||||||
|
|
||||||
/// Called when the backoff expired
|
/// Called when the backoff expired
|
||||||
Future<void> _onTimerElapsed() async {
|
@visibleForTesting
|
||||||
final isReconnecting = await isReconnectionRunning();
|
Future<void> onTimerElapsed() async {
|
||||||
if (shouldReconnect) {
|
_log.finest('Timer elapsed. Waiting for lock...');
|
||||||
if (!isReconnecting) {
|
final shouldContinue = await _timerLock.synchronized(() async {
|
||||||
await performReconnect!();
|
_log.finest('Timer lock aquired');
|
||||||
} else {
|
if (_timer == null) {
|
||||||
// Should never happen.
|
_log.finest(
|
||||||
_log.fine('Backoff timer expired but reconnection is running, so doing nothing.');
|
'The timer is already set to null. Doing nothing.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await getIsReconnecting())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await getShouldReconnect())) {
|
||||||
|
_log.finest(
|
||||||
|
'Should not reconnect. Stopping here.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shouldContinue) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_log.fine('Triggering reconnect');
|
||||||
|
await performReconnect!();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> reset() async {
|
Future<void> reset() async {
|
||||||
_log.finest('Resetting internal state');
|
_log.finest('Resetting internal state');
|
||||||
_counter = 0;
|
await _timerLock.synchronized(() {
|
||||||
await setIsReconnecting(false);
|
_timer?.cancel();
|
||||||
|
|
||||||
if (_timer != null) {
|
|
||||||
_timer!.cancel();
|
|
||||||
_timer = null;
|
_timer = null;
|
||||||
}
|
});
|
||||||
|
await super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onFailure() async {
|
Future<void> onFailure() async {
|
||||||
_log.finest('Failure occured. Starting exponential backoff');
|
final seconds =
|
||||||
_counter++;
|
Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
|
||||||
await setIsReconnecting(true);
|
_log.finest('Failure occured. Starting random backoff with ${seconds}s');
|
||||||
|
|
||||||
if (_timer != null) {
|
await _timerLock.synchronized(() {
|
||||||
_timer!.cancel();
|
_timer?.cancel();
|
||||||
}
|
_timer = Timer(Duration(seconds: seconds), onTimerElapsed);
|
||||||
|
});
|
||||||
// Wait at max 80 seconds.
|
|
||||||
final seconds = min(pow(2, _counter).toInt(), 80);
|
|
||||||
_timer = Timer(Duration(seconds: seconds), _onTimerElapsed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onSuccess() async {
|
Future<void> onSuccess() async {
|
||||||
await reset();
|
await reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
bool isTimerRunning() => _timer != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stub reconnection policy for tests
|
/// A stub reconnection policy for tests.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class TestingReconnectionPolicy extends ReconnectionPolicy {
|
class TestingReconnectionPolicy extends ReconnectionPolicy {
|
||||||
TestingReconnectionPolicy() : super();
|
TestingReconnectionPolicy() : super();
|
||||||
@@ -146,5 +188,29 @@ class TestingReconnectionPolicy extends ReconnectionPolicy {
|
|||||||
Future<void> onFailure() async {}
|
Future<void> onFailure() async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> reset() async {}
|
Future<void> reset() async {
|
||||||
|
await super.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reconnection policy for tests that waits a constant number of seconds before
|
||||||
|
/// attempting a reconnection.
|
||||||
|
@visibleForTesting
|
||||||
|
class TestingSleepReconnectionPolicy extends ReconnectionPolicy {
|
||||||
|
TestingSleepReconnectionPolicy(this._sleepAmount) : super();
|
||||||
|
final int _sleepAmount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onSuccess() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onFailure() async {
|
||||||
|
await Future<void>.delayed(Duration(seconds: _sleepAmount));
|
||||||
|
await performReconnect!();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
await super.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,3 +24,28 @@ int ioctetSortComparator(String a, String b) {
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ioctetSortComparatorRaw(List<int> a, List<int> b) {
|
||||||
|
if (a.isEmpty && b.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isEmpty && b.isNotEmpty) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.isNotEmpty && b.isEmpty) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a[0] == b[0]) {
|
||||||
|
return ioctetSortComparatorRaw(a.sublist(1), b.sublist(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Unknown): Is this correct?
|
||||||
|
if (a[0] < b[0]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
Loaded 100 of 229 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user