Compare commits
168 Commits
moxxmpp-v0
...
c6552968d5
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ko_fi: papatutuwawa
|
||||||
3
.gitignore
vendored
@@ -13,3 +13,6 @@ pubspec.lock
|
|||||||
|
|
||||||
# Omit pubspec override files generated by melos
|
# Omit pubspec override files generated by melos
|
||||||
**/pubspec_overrides.yaml
|
**/pubspec_overrides.yaml
|
||||||
|
|
||||||
|
# Flake results
|
||||||
|
result
|
||||||
|
|||||||
14
.gitlint
Normal file
@@ -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)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$
|
||||||
|
|
||||||
|
|
||||||
|
[body-trailing-whitespace]
|
||||||
|
[body-first-line-empty]
|
||||||
28
.woodpecker.yml
Normal file
@@ -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
|
||||||
19
CONTRIBUTING.md
Normal file
@@ -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.
|
||||||
23
README.md
@@ -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
|
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -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(),
|
||||||
@@ -83,17 +96,39 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
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
|
||||||
@@ -101,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,
|
||||||
@@ -122,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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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.5
|
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+7
|
version: 0.3.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
6
examples_dart/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build output.
|
||||||
|
build/
|
||||||
7
examples_dart/README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Dart Examples
|
||||||
|
|
||||||
|
Run using `dart run bin/<example>.dart`.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- `component.dart`: Use moxxmpp to implement a component using XEP-0114.
|
||||||
30
examples_dart/analysis_options.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
90
examples_dart/bin/component.dart
Normal file
@@ -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));
|
||||||
|
}
|
||||||
26
examples_dart/pubspec.yaml
Normal file
@@ -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
|
||||||
31
flake.lock
generated
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
65
flake.nix
@@ -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;
|
||||||
in {
|
|
||||||
devShell = pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
flutter pinnedJDK android.platform-tools dart # Flutter/Android
|
|
||||||
gitlint # Code hygiene
|
|
||||||
ripgrep # General utilities
|
|
||||||
|
|
||||||
# Flutter dependencies for linux desktop
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
pyyaml
|
||||||
|
requests
|
||||||
|
]);
|
||||||
|
|
||||||
|
moxxmppPubCache = import ./nix/pubcache.moxxmpp.nix {
|
||||||
|
inherit (pkgs) fetchzip runCommand;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
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; [
|
||||||
|
flutter pinnedJDK android.platform-tools dart # Dart
|
||||||
|
gitlint # Code hygiene
|
||||||
|
ripgrep # General utilities
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
|||||||
6
integration_tests/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Files and directories created by pub.
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Conventional directory for build output.
|
||||||
|
build/
|
||||||
5
integration_tests/README.md
Normal file
@@ -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.
|
||||||
1
integration_tests/analysis_options.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include: ../analysis_options.yaml
|
||||||
24
integration_tests/certs/localhost.crt
Normal file
@@ -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-----
|
||||||
28
integration_tests/certs/localhost.key
Normal file
@@ -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-----
|
||||||
62
integration_tests/prosody.cfg.lua
Normal file
@@ -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"
|
||||||
16
integration_tests/pubspec.yaml
Normal file
@@ -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
|
||||||
42
integration_tests/test/component_test.dart
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
74
integration_tests/test/sasl2_test.dart
Normal file
@@ -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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
35
nix/moxxmpp-docs.nix
Normal file
@@ -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
|
||||||
|
'';
|
||||||
|
}
|
||||||
730
nix/moxxmpp.lock
Normal file
@@ -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
|
||||||
814
nix/pubcache.moxxmpp.nix
Normal file
@@ -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
|
||||||
|
'';
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,36 @@
|
|||||||
|
## 0.3.2
|
||||||
|
|
||||||
|
- **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`.
|
||||||
|
|
||||||
|
## 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
|
## 0.1.5
|
||||||
|
|
||||||
- **FEAT**: Message events now contain the stanza error, if available.
|
- **FEAT**: Message events now contain the stanza error, if available.
|
||||||
|
|||||||
@@ -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,98 +0,0 @@
|
|||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:moxxmpp/moxxmpp.dart';
|
|
||||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
Logger.root.level = Level.ALL;
|
|
||||||
Logger.root.onRecord.listen((record) {
|
|
||||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
|
||||||
});
|
|
||||||
final log = Logger('FailureReconnectionTest');
|
|
||||||
|
|
||||||
test('Failing an awaited connection with TestingSleepReconnectionPolicy', () async {
|
|
||||||
var errors = 0;
|
|
||||||
final connection = XmppConnection(
|
|
||||||
TestingSleepReconnectionPolicy(10),
|
|
||||||
TCPSocketWrapper(false),
|
|
||||||
);
|
|
||||||
connection.registerFeatureNegotiators([
|
|
||||||
StartTlsNegotiator(),
|
|
||||||
]);
|
|
||||||
connection.registerManagers([
|
|
||||||
DiscoManager(),
|
|
||||||
RosterManager(),
|
|
||||||
PingManager(),
|
|
||||||
MessageManager(),
|
|
||||||
PresenceManager('http://moxxmpp.example'),
|
|
||||||
]);
|
|
||||||
connection.asBroadcastStream().listen((event) {
|
|
||||||
if (event is ConnectionStateChangedEvent) {
|
|
||||||
if (event.state == XmppConnectionState.error) {
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: true,
|
|
||||||
allowPlainAuth: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await connection.connectAwaitable();
|
|
||||||
log.info('Connection failed as expected');
|
|
||||||
expect(result.success, false);
|
|
||||||
expect(errors, 1);
|
|
||||||
|
|
||||||
log.info('Waiting 20 seconds for unexpected reconnections');
|
|
||||||
await Future.delayed(const Duration(seconds: 20));
|
|
||||||
expect(errors, 1);
|
|
||||||
}, timeout: Timeout.factor(2));
|
|
||||||
|
|
||||||
test('Failing an awaited connection with ExponentialBackoffReconnectionPolicy', () async {
|
|
||||||
var errors = 0;
|
|
||||||
final connection = XmppConnection(
|
|
||||||
ExponentialBackoffReconnectionPolicy(1),
|
|
||||||
TCPSocketWrapper(false),
|
|
||||||
);
|
|
||||||
connection.registerFeatureNegotiators([
|
|
||||||
StartTlsNegotiator(),
|
|
||||||
]);
|
|
||||||
connection.registerManagers([
|
|
||||||
DiscoManager(),
|
|
||||||
RosterManager(),
|
|
||||||
PingManager(),
|
|
||||||
MessageManager(),
|
|
||||||
PresenceManager('http://moxxmpp.example'),
|
|
||||||
]);
|
|
||||||
connection.asBroadcastStream().listen((event) {
|
|
||||||
if (event is ConnectionStateChangedEvent) {
|
|
||||||
if (event.state == XmppConnectionState.error) {
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.setConnectionSettings(
|
|
||||||
ConnectionSettings(
|
|
||||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
|
||||||
password: 'abc123',
|
|
||||||
useDirectTLS: true,
|
|
||||||
allowPlainAuth: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await connection.connectAwaitable();
|
|
||||||
log.info('Connection failed as expected');
|
|
||||||
expect(result.success, false);
|
|
||||||
expect(errors, 1);
|
|
||||||
|
|
||||||
log.info('Waiting 20 seconds for unexpected reconnections');
|
|
||||||
await Future.delayed(const Duration(seconds: 20));
|
|
||||||
expect(errors, 1);
|
|
||||||
}, timeout: Timeout.factor(2));
|
|
||||||
}
|
|
||||||
@@ -1,8 +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/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';
|
||||||
@@ -16,25 +21,27 @@ export 'package:moxxmpp/src/namespaces.dart';
|
|||||||
export 'package:moxxmpp/src/negotiators/manager.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/errors.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/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/errors.dart';
|
||||||
export 'package:moxxmpp/src/roster/roster.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/result.dart';
|
export 'package:moxxmpp/src/types/result.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';
|
||||||
@@ -59,6 +66,7 @@ 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';
|
||||||
@@ -72,9 +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_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';
|
||||||
|
|||||||
94
packages/moxxmpp/lib/src/awaiter.dart
Normal file
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
60
packages/moxxmpp/lib/src/connection_errors.dart
Normal file
@@ -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;
|
||||||
|
}
|
||||||
18
packages/moxxmpp/lib/src/connectivity.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// This manager class is responsible to tell the moxxmpp XmppConnection
|
||||||
|
/// when a connection can be established or not, regarding the network availability.
|
||||||
|
abstract class ConnectivityManager {
|
||||||
|
/// Returns true if a network connection is available. If not, returns false.
|
||||||
|
Future<bool> hasConnection();
|
||||||
|
|
||||||
|
/// Returns a future that resolves once we have a network connection.
|
||||||
|
Future<void> waitForConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An implementation of [ConnectivityManager] that is always connected.
|
||||||
|
class AlwaysConnectedConnectivityManager extends ConnectivityManager {
|
||||||
|
@override
|
||||||
|
Future<bool> hasConnection() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> waitForConnection() async {}
|
||||||
|
}
|
||||||
@@ -1,20 +1,30 @@
|
|||||||
import 'package:moxxmpp/src/socket.dart';
|
import 'package:moxxmpp/src/socket.dart';
|
||||||
|
|
||||||
/// An internal error class
|
/// An internal error class
|
||||||
abstract class XmppError {}
|
// ignore: one_member_abstracts
|
||||||
|
abstract class XmppError {
|
||||||
/// Returned if we could not establish a TCP connection
|
/// Return true if we can recover from the error by attempting a reconnection.
|
||||||
/// to the server.
|
bool isRecoverable();
|
||||||
class NoConnectionError extends XmppError {}
|
}
|
||||||
|
|
||||||
/// Returned if a socket error occured
|
/// Returned if a socket error occured
|
||||||
class SocketError extends XmppError {
|
class SocketError extends XmppError {
|
||||||
SocketError(this.event);
|
SocketError(this.event);
|
||||||
final XmppSocketErrorEvent event;
|
final XmppSocketErrorEvent event;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returned if we time out
|
/// Returned if we time out
|
||||||
class TimeoutError extends XmppError {}
|
class TimeoutError extends XmppError {
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returned if we received a stream error
|
/// Returned if we received a stream error
|
||||||
class StreamError extends XmppError {}
|
class StreamError extends XmppError {
|
||||||
|
// TODO(PapaTutuWawa): Be more precise
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
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/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_0066.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
@@ -18,15 +22,20 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,18 +48,31 @@ 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,
|
required this.body,
|
||||||
@@ -74,6 +96,10 @@ class MessageEvent extends XmppEvent {
|
|||||||
this.funReplacement,
|
this.funReplacement,
|
||||||
this.funCancellation,
|
this.funCancellation,
|
||||||
this.messageRetraction,
|
this.messageRetraction,
|
||||||
|
this.messageCorrectionId,
|
||||||
|
this.messageReactions,
|
||||||
|
this.messageProcessingHints,
|
||||||
|
this.stickerPackId,
|
||||||
});
|
});
|
||||||
final StanzaError? error;
|
final StanzaError? error;
|
||||||
final String body;
|
final String body;
|
||||||
@@ -95,12 +121,16 @@ class MessageEvent extends XmppEvent {
|
|||||||
final String? funCancellation;
|
final String? funCancellation;
|
||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
final MessageRetractionData? messageRetraction;
|
final MessageRetractionData? messageRetraction;
|
||||||
|
final String? messageCorrectionId;
|
||||||
|
final MessageReactions? messageReactions;
|
||||||
|
final List<MessageProcessingHint>? messageProcessingHints;
|
||||||
|
final String? stickerPackId;
|
||||||
final Map<String, dynamic> other;
|
final Map<String, dynamic> other;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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;
|
||||||
}
|
}
|
||||||
@@ -119,9 +149,9 @@ class ChatMarkerEvent extends XmppEvent {
|
|||||||
// 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;
|
||||||
@@ -129,8 +159,10 @@ 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);
|
||||||
|
|
||||||
|
/// The resource that was just bound.
|
||||||
final String resource;
|
final String resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,13 +186,17 @@ 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
|
||||||
class AvatarUpdatedEvent extends XmppEvent {
|
class AvatarUpdatedEvent extends XmppEvent {
|
||||||
AvatarUpdatedEvent({ required this.jid, required this.base64, required this.hash });
|
AvatarUpdatedEvent({
|
||||||
|
required this.jid,
|
||||||
|
required this.base64,
|
||||||
|
required this.hash,
|
||||||
|
});
|
||||||
final String jid;
|
final String jid;
|
||||||
final String base64;
|
final String base64;
|
||||||
final String hash;
|
final String hash;
|
||||||
@@ -168,7 +204,7 @@ class AvatarUpdatedEvent extends XmppEvent {
|
|||||||
|
|
||||||
/// 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;
|
||||||
}
|
}
|
||||||
@@ -181,13 +217,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,3 +245,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;
|
||||||
|
}
|
||||||
|
|||||||
132
packages/moxxmpp/lib/src/handlers/base.dart
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxxmpp/src/errors.dart';
|
||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/parser.dart';
|
||||||
|
import 'package:moxxmpp/src/settings.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
/// A callback for when the [NegotiationsHandler] is done.
|
||||||
|
typedef NegotiationsDoneCallback = Future<void> Function();
|
||||||
|
|
||||||
|
/// A callback for the case that an error occurs while negotiating.
|
||||||
|
typedef ErrorCallback = Future<void> Function(XmppError);
|
||||||
|
|
||||||
|
/// Return true if the current connection is authenticated. If not, return false.
|
||||||
|
typedef IsAuthenticatedFunction = bool Function();
|
||||||
|
|
||||||
|
/// Send a nonza on the stream
|
||||||
|
typedef SendNonzaFunction = void Function(XMLNode);
|
||||||
|
|
||||||
|
/// Returns the connection settings.
|
||||||
|
typedef GetConnectionSettingsFunction = ConnectionSettings Function();
|
||||||
|
|
||||||
|
/// Resets the stream parser's state.
|
||||||
|
typedef ResetStreamParserFunction = void Function();
|
||||||
|
|
||||||
|
/// This class implements the stream feature negotiation for XmppConnection.
|
||||||
|
abstract class NegotiationsHandler {
|
||||||
|
@protected
|
||||||
|
late final Logger log;
|
||||||
|
|
||||||
|
/// Map of all negotiators registered against the handler.
|
||||||
|
@protected
|
||||||
|
final Map<String, XmppFeatureNegotiatorBase> negotiators = {};
|
||||||
|
|
||||||
|
/// Function that is called once the negotiator is done with its stream negotiations.
|
||||||
|
@protected
|
||||||
|
late final NegotiationsDoneCallback onNegotiationsDone;
|
||||||
|
|
||||||
|
/// XmppConnection's handleError method.
|
||||||
|
@protected
|
||||||
|
late final ErrorCallback handleError;
|
||||||
|
|
||||||
|
/// Returns true if the connection is authenticated. If not, returns false.
|
||||||
|
@protected
|
||||||
|
late final IsAuthenticatedFunction isAuthenticated;
|
||||||
|
|
||||||
|
/// Send a nonza over the stream.
|
||||||
|
@protected
|
||||||
|
late final SendNonzaFunction sendNonza;
|
||||||
|
|
||||||
|
/// Get the connection's settings.
|
||||||
|
@protected
|
||||||
|
late final GetConnectionSettingsFunction getConnectionSettings;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
late final ResetStreamParserFunction resetStreamParser;
|
||||||
|
|
||||||
|
/// The id included in the last stream header.
|
||||||
|
@protected
|
||||||
|
String? streamId;
|
||||||
|
|
||||||
|
/// Set the id of the last stream header.
|
||||||
|
void setStreamHeaderId(String? id) {
|
||||||
|
streamId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns, if registered, a negotiator with id [id].
|
||||||
|
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
|
||||||
|
negotiators[id] as T?;
|
||||||
|
|
||||||
|
/// Register the parameters as the corresponding methods in this class. Also
|
||||||
|
/// initializes the logger.
|
||||||
|
void register(
|
||||||
|
NegotiationsDoneCallback onNegotiationsDone,
|
||||||
|
ErrorCallback handleError,
|
||||||
|
IsAuthenticatedFunction isAuthenticated,
|
||||||
|
SendNonzaFunction sendNonza,
|
||||||
|
GetConnectionSettingsFunction getConnectionSettings,
|
||||||
|
ResetStreamParserFunction resetStreamParser,
|
||||||
|
) {
|
||||||
|
this.onNegotiationsDone = onNegotiationsDone;
|
||||||
|
this.handleError = handleError;
|
||||||
|
this.isAuthenticated = isAuthenticated;
|
||||||
|
this.sendNonza = sendNonza;
|
||||||
|
this.getConnectionSettings = getConnectionSettings;
|
||||||
|
this.resetStreamParser = resetStreamParser;
|
||||||
|
log = Logger(toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the xmlns attribute that stanzas should have.
|
||||||
|
String getStanzaNamespace();
|
||||||
|
|
||||||
|
/// Registers the negotiator [negotiator] against this negotiations handler.
|
||||||
|
void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
|
||||||
|
|
||||||
|
/// Sends the stream header.
|
||||||
|
void sendStreamHeader();
|
||||||
|
|
||||||
|
/// Runs the post-register callback of all negotiators.
|
||||||
|
Future<void> runPostRegisterCallback() async {
|
||||||
|
for (final negotiator in negotiators.values) {
|
||||||
|
await negotiator.postRegisterCallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sendEventToNegotiators(XmppEvent event) async {
|
||||||
|
for (final negotiator in negotiators.values) {
|
||||||
|
await negotiator.onXmppEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove [feature] from the stream features we are currently negotiating.
|
||||||
|
void removeNegotiatingFeature(String feature) {}
|
||||||
|
|
||||||
|
/// Resets all registered negotiators and the negotiation handler.
|
||||||
|
@mustCallSuper
|
||||||
|
void reset() {
|
||||||
|
streamId = null;
|
||||||
|
for (final negotiator in negotiators.values) {
|
||||||
|
negotiator.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called whenever the stream buffer outputs a new event [event].
|
||||||
|
Future<void> negotiate(XMPPStreamObject event) async {
|
||||||
|
if (event is XMPPStreamHeader) {
|
||||||
|
streamId = event.attributes['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
227
packages/moxxmpp/lib/src/handlers/client.dart
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:moxxmpp/src/connection_errors.dart';
|
||||||
|
import 'package:moxxmpp/src/handlers/base.dart';
|
||||||
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/parser.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
/// "Nonza" describing the XMPP stream header of a client-to-server connection.
|
||||||
|
class ClientStreamHeaderNonza extends XMLNode {
|
||||||
|
ClientStreamHeaderNonza(JID jid)
|
||||||
|
: super(
|
||||||
|
tag: 'stream:stream',
|
||||||
|
attributes: <String, String>{
|
||||||
|
'xmlns': stanzaXmlns,
|
||||||
|
'version': '1.0',
|
||||||
|
'xmlns:stream': streamXmlns,
|
||||||
|
'to': jid.domain,
|
||||||
|
'from': jid.toBare().toString(),
|
||||||
|
'xml:lang': 'en',
|
||||||
|
},
|
||||||
|
closeTag: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This class implements the stream feature negotiation for usage in client to server
|
||||||
|
/// connections.
|
||||||
|
class ClientToServerNegotiator extends NegotiationsHandler {
|
||||||
|
ClientToServerNegotiator() : super();
|
||||||
|
|
||||||
|
/// Cached list of stream features.
|
||||||
|
final List<XMLNode> _streamFeatures = List.empty(growable: true);
|
||||||
|
|
||||||
|
/// The currently active negotiator.
|
||||||
|
XmppFeatureNegotiatorBase? _currentNegotiator;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getStanzaNamespace() => stanzaXmlns;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
|
||||||
|
negotiators[negotiator.id] = negotiator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reset() {
|
||||||
|
super.reset();
|
||||||
|
|
||||||
|
// Prevent leaking the last active negotiator
|
||||||
|
_currentNegotiator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeNegotiatingFeature(String feature) {
|
||||||
|
_streamFeatures.removeWhere((node) {
|
||||||
|
return node.attributes['xmlns'] == feature;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendStreamHeader() {
|
||||||
|
resetStreamParser();
|
||||||
|
sendNonza(
|
||||||
|
XMLNode(
|
||||||
|
tag: 'xml',
|
||||||
|
attributes: {'version': '1.0'},
|
||||||
|
closeTag: false,
|
||||||
|
isDeclaration: true,
|
||||||
|
children: [
|
||||||
|
ClientStreamHeaderNonza(getConnectionSettings().jid),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if all mandatory features in [features] have been negotiated.
|
||||||
|
/// Otherwise returns false.
|
||||||
|
bool _isMandatoryNegotiationDone(List<XMLNode> features) {
|
||||||
|
return features.every((XMLNode feature) {
|
||||||
|
return feature.firstTag('required') == null &&
|
||||||
|
feature.tag != 'mechanisms';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if we can still negotiate. Returns false if no negotiator is
|
||||||
|
/// matching and ready.
|
||||||
|
bool _isNegotiationPossible(List<XMLNode> features) {
|
||||||
|
return getNextNegotiator(features, log: false) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next negotiator that matches [features]. Returns null if none can be
|
||||||
|
/// picked. If [log] is true, then the list of matching negotiators will be logged.
|
||||||
|
@visibleForTesting
|
||||||
|
XmppFeatureNegotiatorBase? getNextNegotiator(
|
||||||
|
List<XMLNode> features, {
|
||||||
|
bool log = true,
|
||||||
|
}) {
|
||||||
|
final matchingNegotiators =
|
||||||
|
negotiators.values.where((XmppFeatureNegotiatorBase negotiator) {
|
||||||
|
return negotiator.state == NegotiatorState.ready &&
|
||||||
|
negotiator.matchesFeature(features);
|
||||||
|
}).toList()
|
||||||
|
..sort((a, b) => b.priority.compareTo(a.priority));
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
this.log.finest(
|
||||||
|
'List of matching negotiators: ${matchingNegotiators.map((a) => a.id)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingNegotiators.isEmpty) return null;
|
||||||
|
|
||||||
|
return matchingNegotiators.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _executeCurrentNegotiator(XMLNode nonza) async {
|
||||||
|
// If we don't have a negotiator, get one
|
||||||
|
_currentNegotiator ??= getNextNegotiator(_streamFeatures);
|
||||||
|
if (_currentNegotiator == null &&
|
||||||
|
_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||||
|
!_isNegotiationPossible(_streamFeatures)) {
|
||||||
|
log.finest('Negotiations done!');
|
||||||
|
await onNegotiationsDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have a next negotiator, we have to bail
|
||||||
|
if (_currentNegotiator == null &&
|
||||||
|
!_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||||
|
!_isNegotiationPossible(_streamFeatures)) {
|
||||||
|
// We failed before authenticating
|
||||||
|
if (!isAuthenticated()) {
|
||||||
|
log.severe('No negotiator could be picked while unauthenticated');
|
||||||
|
await handleError(NoMatchingAuthenticationMechanismAvailableError());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.severe(
|
||||||
|
'No negotiator could be picked while negotiations are not done',
|
||||||
|
);
|
||||||
|
await handleError(NoAuthenticatorAvailableError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await _currentNegotiator!.negotiate(nonza);
|
||||||
|
if (result.isType<NegotiatorError>()) {
|
||||||
|
log.severe('Negotiator returned an error');
|
||||||
|
await handleError(result.get<NegotiatorError>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final state = result.get<NegotiatorState>();
|
||||||
|
_currentNegotiator!.state = state;
|
||||||
|
switch (state) {
|
||||||
|
case NegotiatorState.ready:
|
||||||
|
return;
|
||||||
|
case NegotiatorState.done:
|
||||||
|
if (_currentNegotiator!.sendStreamHeaderWhenDone) {
|
||||||
|
_currentNegotiator = null;
|
||||||
|
_streamFeatures.clear();
|
||||||
|
sendStreamHeader();
|
||||||
|
} else {
|
||||||
|
removeNegotiatingFeature(_currentNegotiator!.negotiatingXmlns);
|
||||||
|
_currentNegotiator = null;
|
||||||
|
|
||||||
|
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||||
|
!_isNegotiationPossible(_streamFeatures)) {
|
||||||
|
log.finest('Negotiations done!');
|
||||||
|
await onNegotiationsDone();
|
||||||
|
} else {
|
||||||
|
_currentNegotiator = getNextNegotiator(_streamFeatures);
|
||||||
|
log.finest('Chose ${_currentNegotiator!.id} as next negotiator');
|
||||||
|
|
||||||
|
final fakeStanza = XMLNode(
|
||||||
|
tag: 'stream:features',
|
||||||
|
children: _streamFeatures,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _executeCurrentNegotiator(fakeStanza);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NegotiatorState.retryLater:
|
||||||
|
log.finest('Negotiator wants to continue later. Picking new one...');
|
||||||
|
_currentNegotiator!.state = NegotiatorState.ready;
|
||||||
|
|
||||||
|
if (_isMandatoryNegotiationDone(_streamFeatures) &&
|
||||||
|
!_isNegotiationPossible(_streamFeatures)) {
|
||||||
|
log.finest('Negotiations done!');
|
||||||
|
|
||||||
|
await onNegotiationsDone();
|
||||||
|
} else {
|
||||||
|
log.finest('Picking new negotiator...');
|
||||||
|
_currentNegotiator = getNextNegotiator(_streamFeatures);
|
||||||
|
log.finest('Chose $_currentNegotiator as next negotiator');
|
||||||
|
final fakeStanza = XMLNode(
|
||||||
|
tag: 'stream:features',
|
||||||
|
children: _streamFeatures,
|
||||||
|
);
|
||||||
|
await _executeCurrentNegotiator(fakeStanza);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NegotiatorState.skipRest:
|
||||||
|
log.finest(
|
||||||
|
'Negotiator wants to skip the remaining negotiation... Negotiations (assumed) done!',
|
||||||
|
);
|
||||||
|
|
||||||
|
await onNegotiationsDone();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> negotiate(XMPPStreamObject event) async {
|
||||||
|
if (event is XMPPStreamElement) {
|
||||||
|
if (event.node.tag == 'stream:features') {
|
||||||
|
// Store the received stream features
|
||||||
|
_streamFeatures
|
||||||
|
..clear()
|
||||||
|
..addAll(event.node.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _executeCurrentNegotiator(event.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
packages/moxxmpp/lib/src/handlers/component.dart
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:moxxmpp/src/connection_errors.dart';
|
||||||
|
import 'package:moxxmpp/src/handlers/base.dart';
|
||||||
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
|
import 'package:moxxmpp/src/parser.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
|
/// Nonza describing the XMPP stream header.
|
||||||
|
class ComponentStreamHeaderNonza extends XMLNode {
|
||||||
|
ComponentStreamHeaderNonza(JID jid)
|
||||||
|
: assert(jid.isBare(), 'Component JID must be bare'),
|
||||||
|
super(
|
||||||
|
tag: 'stream:stream',
|
||||||
|
attributes: <String, String>{
|
||||||
|
'xmlns': componentAcceptXmlns,
|
||||||
|
'xmlns:stream': streamXmlns,
|
||||||
|
'to': jid.domain,
|
||||||
|
},
|
||||||
|
closeTag: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The states the ComponentToServerNegotiator can be in.
|
||||||
|
enum ComponentToServerState {
|
||||||
|
/// No data has been sent or received yet
|
||||||
|
idle,
|
||||||
|
|
||||||
|
/// Handshake has been sent
|
||||||
|
handshakeSent,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ComponentToServerNegotiator is a NegotiationsHandler that allows writing
|
||||||
|
/// components that adhere to XEP-0114.
|
||||||
|
class ComponentToServerNegotiator extends NegotiationsHandler {
|
||||||
|
ComponentToServerNegotiator();
|
||||||
|
|
||||||
|
/// The state the negotiation handler is currently in
|
||||||
|
ComponentToServerState _state = ComponentToServerState.idle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getStanzaNamespace() => componentAcceptXmlns;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void sendStreamHeader() {
|
||||||
|
resetStreamParser();
|
||||||
|
sendNonza(
|
||||||
|
XMLNode(
|
||||||
|
tag: 'xml',
|
||||||
|
attributes: {'version': '1.0'},
|
||||||
|
closeTag: false,
|
||||||
|
isDeclaration: true,
|
||||||
|
children: [
|
||||||
|
ComponentStreamHeaderNonza(getConnectionSettings().jid),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _computeHandshake(String id) async {
|
||||||
|
final secret = getConnectionSettings().password;
|
||||||
|
return HEX.encode(
|
||||||
|
(await Sha1().hash(utf8.encode('$streamId$secret'))).bytes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> negotiate(XMPPStreamObject event) async {
|
||||||
|
switch (_state) {
|
||||||
|
case ComponentToServerState.idle:
|
||||||
|
if (event is XMPPStreamHeader) {
|
||||||
|
streamId = event.attributes['id'];
|
||||||
|
assert(
|
||||||
|
streamId != null,
|
||||||
|
'The server must respond with a stream header that contains an id',
|
||||||
|
);
|
||||||
|
|
||||||
|
_state = ComponentToServerState.handshakeSent;
|
||||||
|
sendNonza(
|
||||||
|
XMLNode(
|
||||||
|
tag: 'handshake',
|
||||||
|
text: await _computeHandshake(streamId!),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log.severe('Unexpected data received');
|
||||||
|
await handleError(UnexpectedDataError());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ComponentToServerState.handshakeSent:
|
||||||
|
if (event is XMPPStreamElement) {
|
||||||
|
if (event.node.tag == 'handshake' &&
|
||||||
|
event.node.children.isEmpty &&
|
||||||
|
event.node.attributes.isEmpty) {
|
||||||
|
log.info('Successfully authenticated as component');
|
||||||
|
await onNegotiationsDone();
|
||||||
|
} else {
|
||||||
|
log.warning('Handshake failed');
|
||||||
|
await handleError(InvalidHandshakeCredentialsError());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.severe('Unexpected data received');
|
||||||
|
await handleError(UnexpectedDataError());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reset() {
|
||||||
|
_state = ComponentToServerState.idle;
|
||||||
|
|
||||||
|
super.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,31 @@
|
|||||||
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(
|
||||||
|
stanza,
|
||||||
|
awaitable: false,
|
||||||
|
forceEncryption: data.encrypted,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,79 @@
|
|||||||
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 c = jid[i];
|
|
||||||
final eol = i == jid.length - 1;
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case 0: {
|
|
||||||
if (c == '@') {
|
|
||||||
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 != ' ') {
|
final slashParts = jid.split('/');
|
||||||
domain_ = domain_ + c;
|
if (slashParts.length == 1) {
|
||||||
}
|
resourcePart = '';
|
||||||
} else if (c != ' ') {
|
} else {
|
||||||
buffer += c;
|
resourcePart = slashParts.sublist(1).join('/');
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2: {
|
|
||||||
if (eol) {
|
|
||||||
resource_ = buffer;
|
|
||||||
|
|
||||||
if (c != ' ') {
|
assert(
|
||||||
resource_ = resource_ + c;
|
resourcePart.isNotEmpty,
|
||||||
}
|
'Resource part cannot be there and empty',
|
||||||
} 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);
|
||||||
|
|
||||||
|
/// 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 +93,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,27 @@ 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(
|
||||||
|
Stanza stanza, {
|
||||||
|
StanzaFromType addFrom,
|
||||||
|
bool addId,
|
||||||
|
bool awaitable,
|
||||||
|
bool encrypted,
|
||||||
|
bool forceEncryption,
|
||||||
|
}) sendStanza;
|
||||||
|
|
||||||
/// Send a nonza.
|
/// Send a nonza.
|
||||||
final void Function(XMLNode) sendNonza;
|
final void Function(XMLNode) sendNonza;
|
||||||
@@ -39,9 +44,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 +53,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,72 +1,173 @@
|
|||||||
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/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.
|
||||||
/// Must only be called after register has been called on it.
|
/// Must only be called after register has been called on it.
|
||||||
XmppManagerAttributes getAttributes() {
|
XmppManagerAttributes getAttributes() {
|
||||||
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.domain,
|
||||||
|
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.
|
|
||||||
String getId();
|
|
||||||
|
|
||||||
/// Return a name that will be used for logging.
|
/// Return a list of identities that should be included in a disco response.
|
||||||
String getName();
|
List<Identity> getDiscoIdentities() => [];
|
||||||
|
|
||||||
|
/// Return the Id (akin to xmlns) of this manager.
|
||||||
|
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;
|
||||||
|
|
||||||
/// Called when XmppConnection triggers an event
|
/// Called when XmppConnection triggers an event
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {}
|
Future<void> onXmppEvent(XmppEvent event) async {}
|
||||||
|
|
||||||
/// 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(
|
||||||
|
stanza,
|
||||||
|
awaitable: false,
|
||||||
|
forceEncryption: data.encrypted,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
|||||||
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
import 'package:moxxmpp/src/xeps/xep_0380.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
import 'package:moxxmpp/src/xeps/xep_0385.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
import 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
@@ -26,39 +27,48 @@ class StanzaHandlerData with _$StanzaHandlerData {
|
|||||||
dynamic cancelReason,
|
dynamic cancelReason,
|
||||||
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||||
// necessary, e.g. with Message Carbons or OMEMO
|
// necessary, e.g. with Message Carbons or OMEMO
|
||||||
Stanza stanza,
|
Stanza stanza, {
|
||||||
{
|
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
// stanza handlers. MUST NOT be overwritten.
|
||||||
// stanza handlers. MUST NOT be overwritten.
|
@Default(false) bool retransmitted,
|
||||||
@Default(false) bool retransmitted,
|
StatelessMediaSharingData? sims,
|
||||||
StatelessMediaSharingData? sims,
|
StatelessFileSharingData? sfs,
|
||||||
StatelessFileSharingData? sfs,
|
OOBData? oob,
|
||||||
OOBData? oob,
|
StableStanzaId? stableId,
|
||||||
StableStanzaId? stableId,
|
ReplyData? reply,
|
||||||
ReplyData? reply,
|
ChatState? chatState,
|
||||||
ChatState? chatState,
|
@Default(false) bool isCarbon,
|
||||||
@Default(false) bool isCarbon,
|
@Default(false) bool deliveryReceiptRequested,
|
||||||
@Default(false) bool deliveryReceiptRequested,
|
@Default(false) bool isMarkable,
|
||||||
@Default(false) bool isMarkable,
|
// File Upload Notifications
|
||||||
// File Upload Notifications
|
// A notification
|
||||||
// A notification
|
FileMetadataData? fun,
|
||||||
FileMetadataData? fun,
|
// The stanza id this replaces
|
||||||
// The stanza id this replaces
|
String? funReplacement,
|
||||||
String? funReplacement,
|
// The stanza id this cancels
|
||||||
// The stanza id this cancels
|
String? funCancellation,
|
||||||
String? funCancellation,
|
// Whether the stanza was received encrypted
|
||||||
// Whether the stanza was received encrypted
|
@Default(false) bool encrypted,
|
||||||
@Default(false) bool encrypted,
|
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
// The stated type of encryption used, if any was used
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
ExplicitEncryptionType? encryptionType,
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
// Delayed Delivery
|
// to the JID anyway.
|
||||||
DelayedDelivery? delayedDelivery,
|
@Default(false) bool forceEncryption,
|
||||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
// The stated type of encryption used, if any was used
|
||||||
// pass data around.
|
ExplicitEncryptionType? encryptionType,
|
||||||
@Default(<String, dynamic>{}) Map<String, dynamic> other,
|
// Delayed Delivery
|
||||||
// If non-null, then it indicates the origin Id of the message that should be
|
DelayedDelivery? delayedDelivery,
|
||||||
// retracted
|
// This is for stanza handlers that are not part of the XMPP library but still need
|
||||||
MessageRetractionData? messageRetraction,
|
// pass data around.
|
||||||
}
|
@Default(<String, dynamic>{}) Map<String, dynamic> other,
|
||||||
) = _StanzaHandlerData;
|
// If non-null, then it indicates the origin Id of the message that should be
|
||||||
|
// retracted
|
||||||
|
MessageRetractionData? messageRetraction,
|
||||||
|
// If non-null, then the message is a correction for the specified stanza Id
|
||||||
|
String? lastMessageCorrectionSid,
|
||||||
|
// Reactions data
|
||||||
|
MessageReactions? messageReactions,
|
||||||
|
// The Id of the sticker pack this sticker belongs to
|
||||||
|
String? stickerPackId,
|
||||||
|
}) = _StanzaHandlerData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ mixin _$StanzaHandlerData {
|
|||||||
String? get funCancellation =>
|
String? get funCancellation =>
|
||||||
throw _privateConstructorUsedError; // Whether the stanza was received encrypted
|
throw _privateConstructorUsedError; // Whether the stanza was received encrypted
|
||||||
bool get encrypted =>
|
bool get encrypted =>
|
||||||
|
throw _privateConstructorUsedError; // If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
|
// to the JID anyway.
|
||||||
|
bool get forceEncryption =>
|
||||||
throw _privateConstructorUsedError; // The stated type of encryption used, if any was used
|
throw _privateConstructorUsedError; // The stated type of encryption used, if any was used
|
||||||
ExplicitEncryptionType? get encryptionType =>
|
ExplicitEncryptionType? get encryptionType =>
|
||||||
throw _privateConstructorUsedError; // Delayed Delivery
|
throw _privateConstructorUsedError; // Delayed Delivery
|
||||||
@@ -58,7 +63,12 @@ mixin _$StanzaHandlerData {
|
|||||||
throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be
|
throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be
|
||||||
// retracted
|
// retracted
|
||||||
MessageRetractionData? get messageRetraction =>
|
MessageRetractionData? get messageRetraction =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError; // If non-null, then the message is a correction for the specified stanza Id
|
||||||
|
String? get lastMessageCorrectionSid =>
|
||||||
|
throw _privateConstructorUsedError; // Reactions data
|
||||||
|
MessageReactions? get messageReactions =>
|
||||||
|
throw _privateConstructorUsedError; // The Id of the sticker pack this sticker belongs to
|
||||||
|
String? get stickerPackId => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
||||||
@@ -89,10 +99,14 @@ abstract class $StanzaHandlerDataCopyWith<$Res> {
|
|||||||
String? funReplacement,
|
String? funReplacement,
|
||||||
String? funCancellation,
|
String? funCancellation,
|
||||||
bool encrypted,
|
bool encrypted,
|
||||||
|
bool forceEncryption,
|
||||||
ExplicitEncryptionType? encryptionType,
|
ExplicitEncryptionType? encryptionType,
|
||||||
DelayedDelivery? delayedDelivery,
|
DelayedDelivery? delayedDelivery,
|
||||||
Map<String, dynamic> other,
|
Map<String, dynamic> other,
|
||||||
MessageRetractionData? messageRetraction});
|
MessageRetractionData? messageRetraction,
|
||||||
|
String? lastMessageCorrectionSid,
|
||||||
|
MessageReactions? messageReactions,
|
||||||
|
String? stickerPackId});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -124,10 +138,14 @@ class _$StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
Object? funReplacement = freezed,
|
Object? funReplacement = freezed,
|
||||||
Object? funCancellation = freezed,
|
Object? funCancellation = freezed,
|
||||||
Object? encrypted = freezed,
|
Object? encrypted = freezed,
|
||||||
|
Object? forceEncryption = freezed,
|
||||||
Object? encryptionType = freezed,
|
Object? encryptionType = freezed,
|
||||||
Object? delayedDelivery = freezed,
|
Object? delayedDelivery = freezed,
|
||||||
Object? other = freezed,
|
Object? other = freezed,
|
||||||
Object? messageRetraction = freezed,
|
Object? messageRetraction = freezed,
|
||||||
|
Object? lastMessageCorrectionSid = freezed,
|
||||||
|
Object? messageReactions = freezed,
|
||||||
|
Object? stickerPackId = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
done: done == freezed
|
done: done == freezed
|
||||||
@@ -202,6 +220,10 @@ class _$StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
? _value.encrypted
|
? _value.encrypted
|
||||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
: encrypted // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
forceEncryption: forceEncryption == freezed
|
||||||
|
? _value.forceEncryption
|
||||||
|
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
encryptionType: encryptionType == freezed
|
encryptionType: encryptionType == freezed
|
||||||
? _value.encryptionType
|
? _value.encryptionType
|
||||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
: encryptionType // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -218,6 +240,18 @@ class _$StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
? _value.messageRetraction
|
? _value.messageRetraction
|
||||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
||||||
as MessageRetractionData?,
|
as MessageRetractionData?,
|
||||||
|
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
|
||||||
|
? _value.lastMessageCorrectionSid
|
||||||
|
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
messageReactions: messageReactions == freezed
|
||||||
|
? _value.messageReactions
|
||||||
|
: messageReactions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MessageReactions?,
|
||||||
|
stickerPackId: stickerPackId == freezed
|
||||||
|
? _value.stickerPackId
|
||||||
|
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,10 +282,14 @@ abstract class _$$_StanzaHandlerDataCopyWith<$Res>
|
|||||||
String? funReplacement,
|
String? funReplacement,
|
||||||
String? funCancellation,
|
String? funCancellation,
|
||||||
bool encrypted,
|
bool encrypted,
|
||||||
|
bool forceEncryption,
|
||||||
ExplicitEncryptionType? encryptionType,
|
ExplicitEncryptionType? encryptionType,
|
||||||
DelayedDelivery? delayedDelivery,
|
DelayedDelivery? delayedDelivery,
|
||||||
Map<String, dynamic> other,
|
Map<String, dynamic> other,
|
||||||
MessageRetractionData? messageRetraction});
|
MessageRetractionData? messageRetraction,
|
||||||
|
String? lastMessageCorrectionSid,
|
||||||
|
MessageReactions? messageReactions,
|
||||||
|
String? stickerPackId});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -285,10 +323,14 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
Object? funReplacement = freezed,
|
Object? funReplacement = freezed,
|
||||||
Object? funCancellation = freezed,
|
Object? funCancellation = freezed,
|
||||||
Object? encrypted = freezed,
|
Object? encrypted = freezed,
|
||||||
|
Object? forceEncryption = freezed,
|
||||||
Object? encryptionType = freezed,
|
Object? encryptionType = freezed,
|
||||||
Object? delayedDelivery = freezed,
|
Object? delayedDelivery = freezed,
|
||||||
Object? other = freezed,
|
Object? other = freezed,
|
||||||
Object? messageRetraction = freezed,
|
Object? messageRetraction = freezed,
|
||||||
|
Object? lastMessageCorrectionSid = freezed,
|
||||||
|
Object? messageReactions = freezed,
|
||||||
|
Object? stickerPackId = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$_StanzaHandlerData(
|
return _then(_$_StanzaHandlerData(
|
||||||
done == freezed
|
done == freezed
|
||||||
@@ -363,6 +405,10 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
? _value.encrypted
|
? _value.encrypted
|
||||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
: encrypted // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
forceEncryption: forceEncryption == freezed
|
||||||
|
? _value.forceEncryption
|
||||||
|
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
encryptionType: encryptionType == freezed
|
encryptionType: encryptionType == freezed
|
||||||
? _value.encryptionType
|
? _value.encryptionType
|
||||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
: encryptionType // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -379,6 +425,18 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
? _value.messageRetraction
|
? _value.messageRetraction
|
||||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
||||||
as MessageRetractionData?,
|
as MessageRetractionData?,
|
||||||
|
lastMessageCorrectionSid: lastMessageCorrectionSid == freezed
|
||||||
|
? _value.lastMessageCorrectionSid
|
||||||
|
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
messageReactions: messageReactions == freezed
|
||||||
|
? _value.messageReactions
|
||||||
|
: messageReactions // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MessageReactions?,
|
||||||
|
stickerPackId: stickerPackId == freezed
|
||||||
|
? _value.stickerPackId
|
||||||
|
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,10 +459,14 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
this.funReplacement,
|
this.funReplacement,
|
||||||
this.funCancellation,
|
this.funCancellation,
|
||||||
this.encrypted = false,
|
this.encrypted = false,
|
||||||
|
this.forceEncryption = false,
|
||||||
this.encryptionType,
|
this.encryptionType,
|
||||||
this.delayedDelivery,
|
this.delayedDelivery,
|
||||||
final Map<String, dynamic> other = const <String, dynamic>{},
|
final Map<String, dynamic> other = const <String, dynamic>{},
|
||||||
this.messageRetraction})
|
this.messageRetraction,
|
||||||
|
this.lastMessageCorrectionSid,
|
||||||
|
this.messageReactions,
|
||||||
|
this.stickerPackId})
|
||||||
: _other = other;
|
: _other = other;
|
||||||
|
|
||||||
// Indicates to the runner that processing is now done. This means that all
|
// Indicates to the runner that processing is now done. This means that all
|
||||||
@@ -462,6 +524,13 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
|
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
|
// to the JID anyway.
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool forceEncryption;
|
||||||
// The stated type of encryption used, if any was used
|
// The stated type of encryption used, if any was used
|
||||||
@override
|
@override
|
||||||
final ExplicitEncryptionType? encryptionType;
|
final ExplicitEncryptionType? encryptionType;
|
||||||
@@ -484,10 +553,19 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
// retracted
|
// retracted
|
||||||
@override
|
@override
|
||||||
final MessageRetractionData? messageRetraction;
|
final MessageRetractionData? messageRetraction;
|
||||||
|
// If non-null, then the message is a correction for the specified stanza Id
|
||||||
|
@override
|
||||||
|
final String? lastMessageCorrectionSid;
|
||||||
|
// Reactions data
|
||||||
|
@override
|
||||||
|
final MessageReactions? messageReactions;
|
||||||
|
// The Id of the sticker pack this sticker belongs to
|
||||||
|
@override
|
||||||
|
final String? stickerPackId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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, messageRetraction: $messageRetraction)';
|
return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, stableId: $stableId, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, forceEncryption: $forceEncryption, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction, lastMessageCorrectionSid: $lastMessageCorrectionSid, messageReactions: $messageReactions, stickerPackId: $stickerPackId)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -519,13 +597,21 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.funCancellation, funCancellation) &&
|
.equals(other.funCancellation, funCancellation) &&
|
||||||
const DeepCollectionEquality().equals(other.encrypted, encrypted) &&
|
const DeepCollectionEquality().equals(other.encrypted, encrypted) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.forceEncryption, forceEncryption) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.encryptionType, encryptionType) &&
|
.equals(other.encryptionType, encryptionType) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.delayedDelivery, delayedDelivery) &&
|
.equals(other.delayedDelivery, delayedDelivery) &&
|
||||||
const DeepCollectionEquality().equals(other._other, this._other) &&
|
const DeepCollectionEquality().equals(other._other, this._other) &&
|
||||||
const DeepCollectionEquality()
|
const DeepCollectionEquality()
|
||||||
.equals(other.messageRetraction, messageRetraction));
|
.equals(other.messageRetraction, messageRetraction) &&
|
||||||
|
const DeepCollectionEquality().equals(
|
||||||
|
other.lastMessageCorrectionSid, lastMessageCorrectionSid) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.messageReactions, messageReactions) &&
|
||||||
|
const DeepCollectionEquality()
|
||||||
|
.equals(other.stickerPackId, stickerPackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -549,10 +635,14 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
const DeepCollectionEquality().hash(funReplacement),
|
const DeepCollectionEquality().hash(funReplacement),
|
||||||
const DeepCollectionEquality().hash(funCancellation),
|
const DeepCollectionEquality().hash(funCancellation),
|
||||||
const DeepCollectionEquality().hash(encrypted),
|
const DeepCollectionEquality().hash(encrypted),
|
||||||
|
const DeepCollectionEquality().hash(forceEncryption),
|
||||||
const DeepCollectionEquality().hash(encryptionType),
|
const DeepCollectionEquality().hash(encryptionType),
|
||||||
const DeepCollectionEquality().hash(delayedDelivery),
|
const DeepCollectionEquality().hash(delayedDelivery),
|
||||||
const DeepCollectionEquality().hash(_other),
|
const DeepCollectionEquality().hash(_other),
|
||||||
const DeepCollectionEquality().hash(messageRetraction)
|
const DeepCollectionEquality().hash(messageRetraction),
|
||||||
|
const DeepCollectionEquality().hash(lastMessageCorrectionSid),
|
||||||
|
const DeepCollectionEquality().hash(messageReactions),
|
||||||
|
const DeepCollectionEquality().hash(stickerPackId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@@ -579,10 +669,14 @@ abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|||||||
final String? funReplacement,
|
final String? funReplacement,
|
||||||
final String? funCancellation,
|
final String? funCancellation,
|
||||||
final bool encrypted,
|
final bool encrypted,
|
||||||
|
final bool forceEncryption,
|
||||||
final ExplicitEncryptionType? encryptionType,
|
final ExplicitEncryptionType? encryptionType,
|
||||||
final DelayedDelivery? delayedDelivery,
|
final DelayedDelivery? delayedDelivery,
|
||||||
final Map<String, dynamic> other,
|
final Map<String, dynamic> other,
|
||||||
final MessageRetractionData? messageRetraction}) = _$_StanzaHandlerData;
|
final MessageRetractionData? messageRetraction,
|
||||||
|
final String? lastMessageCorrectionSid,
|
||||||
|
final MessageReactions? messageReactions,
|
||||||
|
final String? stickerPackId}) = _$_StanzaHandlerData;
|
||||||
|
|
||||||
@override // Indicates to the runner that processing is now done. This means that all
|
@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.
|
// pre-processing is done and no other handlers should be consulted.
|
||||||
@@ -625,6 +719,11 @@ abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|||||||
String? get funCancellation;
|
String? get funCancellation;
|
||||||
@override // Whether the stanza was received encrypted
|
@override // Whether the stanza was received encrypted
|
||||||
bool get encrypted;
|
bool get encrypted;
|
||||||
|
@override // If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
|
// to the JID anyway.
|
||||||
|
bool get forceEncryption;
|
||||||
@override // The stated type of encryption used, if any was used
|
@override // The stated type of encryption used, if any was used
|
||||||
ExplicitEncryptionType? get encryptionType;
|
ExplicitEncryptionType? get encryptionType;
|
||||||
@override // Delayed Delivery
|
@override // Delayed Delivery
|
||||||
@@ -635,6 +734,12 @@ abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|||||||
@override // If non-null, then it indicates the origin Id of the message that should be
|
@override // If non-null, then it indicates the origin Id of the message that should be
|
||||||
// retracted
|
// retracted
|
||||||
MessageRetractionData? get messageRetraction;
|
MessageRetractionData? get messageRetraction;
|
||||||
|
@override // If non-null, then the message is a correction for the specified stanza Id
|
||||||
|
String? get lastMessageCorrectionSid;
|
||||||
|
@override // Reactions data
|
||||||
|
MessageReactions? get messageReactions;
|
||||||
|
@override // The Id of the sticker pack this sticker belongs to
|
||||||
|
String? get stickerPackId;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
||||||
|
|||||||
@@ -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,27 +1,33 @@
|
|||||||
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 messageRetractionManager = 'org.moxxmpp.messageretractionmanager';
|
||||||
|
const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
|
||||||
|
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
||||||
|
const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||||
|
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@@ -11,13 +12,22 @@ import 'package:moxxmpp/src/xeps/staging/file_upload_notification.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_0085.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0184.dart';
|
import 'package:moxxmpp/src/xeps/xep_0184.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0308.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
import 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
import 'package:moxxmpp/src/xeps/xep_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_0448.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
|
|
||||||
|
/// Data used to build a message stanza.
|
||||||
|
///
|
||||||
|
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
|
||||||
|
/// added. This is recommended when sharing files but may cause issues when the message
|
||||||
|
/// stanza should include a SFS element without any fallbacks.
|
||||||
class MessageDetails {
|
class MessageDetails {
|
||||||
const MessageDetails({
|
const MessageDetails({
|
||||||
required this.to,
|
required this.to,
|
||||||
@@ -36,6 +46,11 @@ class MessageDetails {
|
|||||||
this.funCancellation,
|
this.funCancellation,
|
||||||
this.shouldEncrypt = false,
|
this.shouldEncrypt = false,
|
||||||
this.messageRetraction,
|
this.messageRetraction,
|
||||||
|
this.lastMessageCorrectionId,
|
||||||
|
this.messageReactions,
|
||||||
|
this.messageProcessingHints,
|
||||||
|
this.stickerPackId,
|
||||||
|
this.setOOBFallbackBody = true,
|
||||||
});
|
});
|
||||||
final String to;
|
final String to;
|
||||||
final String? body;
|
final String? body;
|
||||||
@@ -53,54 +68,70 @@ class MessageDetails {
|
|||||||
final String? funCancellation;
|
final String? funCancellation;
|
||||||
final bool shouldEncrypt;
|
final bool shouldEncrypt;
|
||||||
final MessageRetractionData? messageRetraction;
|
final MessageRetractionData? messageRetraction;
|
||||||
|
final String? lastMessageCorrectionId;
|
||||||
|
final MessageReactions? messageReactions;
|
||||||
|
final String? stickerPackId;
|
||||||
|
final List<MessageProcessingHint>? messageProcessingHints;
|
||||||
|
final bool setOOBFallbackBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageManager extends XmppManagerBase {
|
class MessageManager extends XmppManagerBase {
|
||||||
@override
|
MessageManager() : super(messageManager);
|
||||||
String getId() => messageManager;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getName() => 'MessageManager';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'message',
|
stanzaTag: 'message',
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
priority: -100,
|
priority: -100,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(Stanza _, StanzaHandlerData state) async {
|
Future<StanzaHandlerData> _onMessage(
|
||||||
|
Stanza _,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
final message = state.stanza;
|
final message = state.stanza;
|
||||||
final body = message.firstTag('body');
|
final body = message.firstTag('body');
|
||||||
|
|
||||||
getAttributes().sendEvent(MessageEvent(
|
final hints = List<MessageProcessingHint>.empty(growable: true);
|
||||||
body: body != null ? body.innerText() : '',
|
for (final element
|
||||||
fromJid: JID.fromString(message.attributes['from']! as String),
|
in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
|
||||||
toJid: JID.fromString(message.attributes['to']! as String),
|
hints.add(messageProcessingHintFromXml(element));
|
||||||
sid: message.attributes['id']! as String,
|
}
|
||||||
stanzaId: state.stableId ?? const StableStanzaId(),
|
|
||||||
isCarbon: state.isCarbon,
|
getAttributes().sendEvent(
|
||||||
deliveryReceiptRequested: state.deliveryReceiptRequested,
|
MessageEvent(
|
||||||
isMarkable: state.isMarkable,
|
body: body != null ? body.innerText() : '',
|
||||||
type: message.attributes['type'] as String?,
|
fromJid: JID.fromString(message.attributes['from']! as String),
|
||||||
oob: state.oob,
|
toJid: JID.fromString(message.attributes['to']! as String),
|
||||||
sfs: state.sfs,
|
sid: message.attributes['id']! as String,
|
||||||
sims: state.sims,
|
stanzaId: state.stableId ?? const StableStanzaId(),
|
||||||
reply: state.reply,
|
isCarbon: state.isCarbon,
|
||||||
chatState: state.chatState,
|
deliveryReceiptRequested: state.deliveryReceiptRequested,
|
||||||
fun: state.fun,
|
isMarkable: state.isMarkable,
|
||||||
funReplacement: state.funReplacement,
|
type: message.attributes['type'] as String?,
|
||||||
funCancellation: state.funCancellation,
|
oob: state.oob,
|
||||||
encrypted: state.encrypted,
|
sfs: state.sfs,
|
||||||
messageRetraction: state.messageRetraction,
|
sims: state.sims,
|
||||||
other: state.other,
|
reply: state.reply,
|
||||||
error: StanzaError.fromStanza(message),
|
chatState: state.chatState,
|
||||||
),);
|
fun: state.fun,
|
||||||
|
funReplacement: state.funReplacement,
|
||||||
|
funCancellation: state.funCancellation,
|
||||||
|
encrypted: state.encrypted,
|
||||||
|
messageRetraction: state.messageRetraction,
|
||||||
|
messageCorrectionId: state.lastMessageCorrectionSid,
|
||||||
|
messageReactions: state.messageReactions,
|
||||||
|
messageProcessingHints: hints.isEmpty ? null : hints,
|
||||||
|
stickerPackId: state.stickerPackId,
|
||||||
|
other: state.other,
|
||||||
|
error: StanzaError.fromStanza(message),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
return state.copyWith(done: true);
|
||||||
}
|
}
|
||||||
@@ -111,6 +142,14 @@ class MessageManager extends XmppManagerBase {
|
|||||||
/// element to this id. If originId is non-null, then it will create an "origin-id"
|
/// 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.
|
/// child in the message stanza and set its id to originId.
|
||||||
void sendMessage(MessageDetails details) {
|
void sendMessage(MessageDetails details) {
|
||||||
|
assert(
|
||||||
|
implies(
|
||||||
|
details.quoteBody != null,
|
||||||
|
details.quoteFrom != null && details.quoteId != null,
|
||||||
|
),
|
||||||
|
'When quoting a message, then quoteFrom and quoteId must also be non-null',
|
||||||
|
);
|
||||||
|
|
||||||
final stanza = Stanza.message(
|
final stanza = Stanza.message(
|
||||||
to: details.to,
|
to: details.to,
|
||||||
type: 'chat',
|
type: 'chat',
|
||||||
@@ -119,35 +158,30 @@ class MessageManager extends XmppManagerBase {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (details.quoteBody != null) {
|
if (details.quoteBody != null) {
|
||||||
final fallback = '> ${details.quoteBody!}';
|
final quote = QuoteData.fromBodies(details.quoteBody!, details.body!);
|
||||||
|
|
||||||
stanza
|
stanza
|
||||||
..addChild(
|
..addChild(
|
||||||
XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
|
XMLNode(tag: 'body', text: quote.body),
|
||||||
)
|
)
|
||||||
..addChild(
|
..addChild(
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'reply',
|
tag: 'reply',
|
||||||
xmlns: replyXmlns,
|
xmlns: replyXmlns,
|
||||||
attributes: {
|
attributes: {'to': details.quoteFrom!, 'id': details.quoteId!},
|
||||||
'to': details.quoteFrom!,
|
|
||||||
'id': details.quoteId!
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
..addChild(
|
..addChild(
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'fallback',
|
tag: 'fallback',
|
||||||
xmlns: fallbackXmlns,
|
xmlns: fallbackXmlns,
|
||||||
attributes: {
|
attributes: {'for': replyXmlns},
|
||||||
'for': replyXmlns
|
|
||||||
},
|
|
||||||
children: [
|
children: [
|
||||||
XMLNode(
|
XMLNode(
|
||||||
tag: 'body',
|
tag: 'body',
|
||||||
attributes: <String, String>{
|
attributes: <String, String>{
|
||||||
'start': '0',
|
'start': '0',
|
||||||
'end': '${fallback.length}'
|
'end': '${quote.fallbackLength}',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -155,7 +189,7 @@ class MessageManager extends XmppManagerBase {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var body = details.body;
|
var body = details.body;
|
||||||
if (details.sfs != null) {
|
if (details.sfs != null && details.setOOBFallbackBody) {
|
||||||
// TODO(Unknown): Maybe find a better solution
|
// TODO(Unknown): Maybe find a better solution
|
||||||
final firstSource = details.sfs!.sources.first;
|
final firstSource = details.sfs!.sources.first;
|
||||||
if (firstSource is StatelessFileSharingUrlSource) {
|
if (firstSource is StatelessFileSharingUrlSource) {
|
||||||
@@ -167,9 +201,11 @@ class MessageManager extends XmppManagerBase {
|
|||||||
body = details.messageRetraction!.fallback;
|
body = details.messageRetraction!.fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
stanza.addChild(
|
if (body != null) {
|
||||||
XMLNode(tag: 'body', text: body),
|
stanza.addChild(
|
||||||
);
|
XMLNode(tag: 'body', text: body),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.requestDeliveryReceipt) {
|
if (details.requestDeliveryReceipt) {
|
||||||
@@ -186,16 +222,20 @@ class MessageManager extends XmppManagerBase {
|
|||||||
stanza.addChild(details.sfs!.toXML());
|
stanza.addChild(details.sfs!.toXML());
|
||||||
|
|
||||||
final source = details.sfs!.sources.first;
|
final source = details.sfs!.sources.first;
|
||||||
if (source is StatelessFileSharingUrlSource) {
|
if (source is StatelessFileSharingUrlSource &&
|
||||||
|
details.setOOBFallbackBody) {
|
||||||
// SFS recommends OOB as a fallback
|
// SFS recommends OOB as a fallback
|
||||||
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
|
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.chatState != null) {
|
if (details.chatState != null) {
|
||||||
stanza.addChild(
|
stanza.addChild(
|
||||||
// TODO(Unknown): Move this into xep_0085.dart
|
// TODO(Unknown): Move this into xep_0085.dart
|
||||||
XMLNode.xmlns(tag: chatStateToString(details.chatState!), xmlns: chatStateXmlns),
|
XMLNode.xmlns(
|
||||||
|
tag: chatStateToString(details.chatState!),
|
||||||
|
xmlns: chatStateXmlns,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +289,37 @@ class MessageManager extends XmppManagerBase {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (details.lastMessageCorrectionId != null) {
|
||||||
|
stanza.addChild(
|
||||||
|
makeLastMessageCorrectionEdit(
|
||||||
|
details.lastMessageCorrectionId!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.messageReactions != null) {
|
||||||
|
stanza.addChild(details.messageReactions!.toXml());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.messageProcessingHints != null) {
|
||||||
|
for (final hint in details.messageProcessingHints!) {
|
||||||
|
stanza.addChild(hint.toXml());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.stickerPackId != null) {
|
||||||
|
stanza.addChild(
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'sticker',
|
||||||
|
xmlns: stickersXmlns,
|
||||||
|
attributes: {
|
||||||
|
'pack': details.stickerPackId!,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getAttributes().sendStanza(stanza, awaitable: false);
|
getAttributes().sendStanza(stanza, awaitable: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,9 +28,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 +44,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 +74,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';
|
||||||
@@ -111,6 +113,12 @@ 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';
|
||||||
|
|
||||||
@@ -120,9 +128,12 @@ const fasteningXmlns = 'urn:xmpp:fasten:0';
|
|||||||
// XEP-0424
|
// XEP-0424
|
||||||
const messageRetractionXmlns = 'urn:xmpp:message-retract:0';
|
const messageRetractionXmlns = 'urn:xmpp:message-retract:0';
|
||||||
|
|
||||||
// XEp-0428
|
// XEP-0428
|
||||||
const fallbackIndicationXmlns = 'urn:xmpp:fallback:0';
|
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';
|
||||||
|
|
||||||
@@ -131,13 +142,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';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -7,3 +7,7 @@ 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';
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
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/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';
|
||||||
@@ -27,6 +29,7 @@ abstract class NegotiatorError extends XmppError {}
|
|||||||
class NegotiatorAttributes {
|
class NegotiatorAttributes {
|
||||||
const NegotiatorAttributes(
|
const NegotiatorAttributes(
|
||||||
this.sendNonza,
|
this.sendNonza,
|
||||||
|
this.getConnection,
|
||||||
this.getConnectionSettings,
|
this.getConnectionSettings,
|
||||||
this.sendEvent,
|
this.sendEvent,
|
||||||
this.getNegotiatorById,
|
this.getNegotiatorById,
|
||||||
@@ -34,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;
|
||||||
@@ -70,26 +103,30 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
|
|
||||||
/// The Id of the negotiator
|
/// The Id of the negotiator
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// The state the negotiator is currently in
|
/// The state the negotiator is currently in
|
||||||
NegotiatorState state;
|
NegotiatorState state;
|
||||||
|
|
||||||
late NegotiatorAttributes _attributes;
|
late NegotiatorAttributes _attributes;
|
||||||
|
|
||||||
/// Register the negotiator against a connection class by means of [attributes].
|
/// Register the negotiator against a connection class by means of [attributes].
|
||||||
void register(NegotiatorAttributes attributes) {
|
void register(NegotiatorAttributes attributes) {
|
||||||
_attributes = attributes;
|
_attributes = attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if a feature in [features], which are the children of the
|
/// Returns true if a feature in [features], which are the children of the
|
||||||
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
/// <stream:features /> nonza, can be negotiated. Otherwise, returns false.
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
return firstWhereOrNull(
|
return 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.
|
||||||
@@ -105,6 +142,11 @@ abstract class XmppFeatureNegotiatorBase {
|
|||||||
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,3 +0,0 @@
|
|||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
|
||||||
|
|
||||||
class SaslFailedError extends NegotiatorError {}
|
|
||||||
@@ -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/errors.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:moxxmpp/src/types/result.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<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
|
|
||||||
if (!_authSent) {
|
|
||||||
final settings = attributes.getConnectionSettings();
|
|
||||||
attributes.sendNonza(
|
|
||||||
SaslPlainAuthNonza(settings.jid.local, settings.password),
|
|
||||||
redact: SaslPlainAuthNonza('******', '******').toXml(),
|
|
||||||
);
|
|
||||||
_authSent = true;
|
|
||||||
return const Result(NegotiatorState.ready);
|
|
||||||
} else {
|
|
||||||
final tag = nonza.tag;
|
|
||||||
if (tag == 'success') {
|
|
||||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
} else {
|
|
||||||
// We assume it's a <failure/>
|
|
||||||
final error = nonza.children.first.tag;
|
|
||||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
return Result(SaslFailedError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_authSent = false;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,265 +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/errors.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:moxxmpp/src/types/result.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int pbkdfBitsFromHash(ScramHashType type) {
|
|
||||||
switch (type) {
|
|
||||||
// NOTE: SHA1 is 20 octets long => 20 octets * 8 bits/octet
|
|
||||||
case ScramHashType.sha1: return 160;
|
|
||||||
// NOTE: SHA256 is 32 octets long => 32 octets * 8 bits/octet
|
|
||||||
case ScramHashType.sha256: return 256;
|
|
||||||
// NOTE: SHA512 is 64 octets long => 64 octets * 8 bits/octet
|
|
||||||
case ScramHashType.sha512: return 512;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: pbkdfBitsFromHash(hashType),
|
|
||||||
);
|
|
||||||
|
|
||||||
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<Result<NegotiatorState, NegotiatorError>> 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(),
|
|
||||||
);
|
|
||||||
return const Result(NegotiatorState.ready);
|
|
||||||
case ScramState.initialMessageSent:
|
|
||||||
if (nonza.tag != 'challenge') {
|
|
||||||
final error = nonza.children.first.tag;
|
|
||||||
await attributes.sendEvent(AuthenticationFailedEvent(error));
|
|
||||||
|
|
||||||
_scramState = ScramState.error;
|
|
||||||
return Result(SaslFailedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
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 const Result(NegotiatorState.ready);
|
|
||||||
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;
|
|
||||||
return Result(SaslFailedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
return Result(SaslFailedError());
|
|
||||||
}
|
|
||||||
|
|
||||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
|
||||||
return const Result(NegotiatorState.done);
|
|
||||||
case ScramState.error:
|
|
||||||
return Result(SaslFailedError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void reset() {
|
|
||||||
_scramState = ScramState.preSent;
|
|
||||||
|
|
||||||
super.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
165
packages/moxxmpp/lib/src/parser.dart
Normal file
@@ -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,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@@ -6,101 +7,113 @@ 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/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0115.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0414.dart';
|
|
||||||
|
|
||||||
|
/// A function that will be called when presence, outside of subscription request
|
||||||
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
|
/// presence.
|
||||||
|
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
||||||
|
|
||||||
|
/// A mandatory manager that handles initial presence sending, sending of subscription
|
||||||
|
/// request management requests and triggers events for incoming presence stanzas.
|
||||||
class PresenceManager extends XmppManagerBase {
|
class PresenceManager extends XmppManagerBase {
|
||||||
PresenceManager(this._capHashNode) : _capabilityHash = null, super();
|
PresenceManager() : super(presenceManager);
|
||||||
String? _capabilityHash;
|
|
||||||
final String _capHashNode;
|
|
||||||
|
|
||||||
String get capabilityHashNode => _capHashNode;
|
/// The list of pre-send callbacks.
|
||||||
|
final List<PresencePreSendCallback> _presenceCallbacks =
|
||||||
@override
|
List.empty(growable: true);
|
||||||
String getId() => presenceManager;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getName() => 'PresenceManager';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'presence',
|
stanzaTag: 'presence',
|
||||||
callback: _onPresence,
|
callback: _onPresence,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@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
|
||||||
|
final sm = getAttributes().getNegotiatorById<StreamManagementNegotiator>(
|
||||||
|
streamManagementNegotiator,
|
||||||
|
);
|
||||||
|
final isResumed = sm?.isResumed ?? false;
|
||||||
|
if (!isResumed) {
|
||||||
|
unawaited(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.copyWith(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));
|
getAttributes().sendEvent(
|
||||||
|
PresenceReceivedEvent(JID.fromString(presence.from!), presence),
|
||||||
|
);
|
||||||
return state.copyWith(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(
|
Stanza.presence(
|
||||||
from: attrs.getFullJID().toString(),
|
from: attrs.getFullJID().toString(),
|
||||||
children: [
|
children: children,
|
||||||
XMLNode(
|
|
||||||
tag: 'show',
|
|
||||||
text: 'chat',
|
|
||||||
),
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'c',
|
|
||||||
xmlns: capsXmlns,
|
|
||||||
attributes: {
|
|
||||||
'hash': 'sha-1',
|
|
||||||
'node': _capHashNode,
|
|
||||||
'ver': await getCapabilityHash()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
awaitable: false,
|
||||||
|
addId: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +126,7 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
addFrom: StanzaFromType.full,
|
addFrom: StanzaFromType.full,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a subscription request to [to].
|
/// Sends a subscription request to [to].
|
||||||
void sendSubscriptionRequest(String to) {
|
void sendSubscriptionRequest(String to) {
|
||||||
getAttributes().sendStanza(
|
getAttributes().sendStanza(
|
||||||
|
|||||||
@@ -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.
|
|
||||||
void Function()? triggerConnectionLost;
|
|
||||||
/// Indicate if should try to reconnect.
|
|
||||||
bool _shouldAttemptReconnection;
|
|
||||||
/// Indicate if a reconnection attempt is currently running.
|
|
||||||
bool _isReconnecting;
|
|
||||||
/// And the corresponding lock
|
|
||||||
final Lock _isReconnectingLock;
|
|
||||||
|
|
||||||
/// Called by XmppConnection to register the policy.
|
|
||||||
void register(Future<void> Function() performReconnect, void Function() triggerConnectionLost) {
|
|
||||||
this.performReconnect = performReconnect;
|
|
||||||
this.triggerConnectionLost = triggerConnectionLost;
|
|
||||||
|
|
||||||
unawaited(reset());
|
final Lock _lock = Lock();
|
||||||
|
|
||||||
|
/// Indicate if a reconnection attempt is currently running.
|
||||||
|
bool _isReconnecting = false;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
void register(
|
||||||
|
PerformReconnectFunction performReconnect,
|
||||||
|
) {
|
||||||
|
this.performReconnect = performReconnect;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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,100 +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(this._maxBackoffTime)
|
/// The maximum time in seconds that a backoff should be.
|
||||||
: _counter = 0,
|
|
||||||
_log = Logger('ExponentialBackoffReconnectionPolicy'),
|
|
||||||
super();
|
|
||||||
final int _maxBackoffTime;
|
final int _maxBackoffTime;
|
||||||
int _counter;
|
|
||||||
|
/// The minimum time in seconds that a backoff should be.
|
||||||
|
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 setIsReconnecting(true);
|
_log.finest('Timer lock aquired');
|
||||||
await performReconnect!();
|
if (_timer == null) {
|
||||||
} else {
|
_log.finest(
|
||||||
// Should never happen.
|
'The timer is already set to null. Doing nothing.',
|
||||||
_log.fine('Backoff timer expired but reconnection is running, so 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;
|
||||||
|
_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(min(pow(2, _counter).toInt(), 80), _maxBackoffTime);
|
|
||||||
_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();
|
||||||
@@ -147,7 +188,9 @@ 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
|
/// A reconnection policy for tests that waits a constant number of seconds before
|
||||||
@@ -156,7 +199,7 @@ class TestingReconnectionPolicy extends ReconnectionPolicy {
|
|||||||
class TestingSleepReconnectionPolicy extends ReconnectionPolicy {
|
class TestingSleepReconnectionPolicy extends ReconnectionPolicy {
|
||||||
TestingSleepReconnectionPolicy(this._sleepAmount) : super();
|
TestingSleepReconnectionPolicy(this._sleepAmount) : super();
|
||||||
final int _sleepAmount;
|
final int _sleepAmount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onSuccess() async {}
|
Future<void> onSuccess() async {}
|
||||||
|
|
||||||
@@ -167,5 +210,7 @@ class TestingSleepReconnectionPolicy extends ReconnectionPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> reset() async {}
|
Future<void> reset() async {
|
||||||
|
await super.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,31 @@ int ioctetSortComparator(String a, String b) {
|
|||||||
if (a.codeUnitAt(0) < b.codeUnitAt(0)) {
|
if (a.codeUnitAt(0) < b.codeUnitAt(0)) {
|
||||||
return -1;
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
@@ -8,25 +8,41 @@ import 'package:moxxmpp/src/types/result.dart';
|
|||||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class ResourceBindingFailedError extends NegotiatorError {}
|
class ResourceBindingFailedError extends NegotiatorError {
|
||||||
|
@override
|
||||||
|
bool isRecoverable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A negotiator that implements resource binding against a random server-provided
|
||||||
|
/// resource.
|
||||||
class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
||||||
|
ResourceBindingNegotiator()
|
||||||
|
: super(0, false, bindXmlns, resourceBindingNegotiator);
|
||||||
|
|
||||||
ResourceBindingNegotiator() : _requestSent = false, super(0, false, bindXmlns, resourceBindingNegotiator);
|
/// Flag indicating the state of the negotiator:
|
||||||
bool _requestSent;
|
/// - True: We sent a binding request
|
||||||
|
/// - False: We have not yet sent the binding request
|
||||||
|
bool _requestSent = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matchesFeature(List<XMLNode> features) {
|
bool matchesFeature(List<XMLNode> features) {
|
||||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager);
|
final sm = attributes.getManagerById<StreamManagementManager>(smManager);
|
||||||
if (sm != null) {
|
if (sm != null) {
|
||||||
return super.matchesFeature(features) && !sm.streamResumed && attributes.isAuthenticated();
|
return super.matchesFeature(features) &&
|
||||||
|
!sm.streamResumed &&
|
||||||
|
attributes.isAuthenticated() &&
|
||||||
|
attributes.getConnection().resource.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.matchesFeature(features) && attributes.isAuthenticated();
|
return super.matchesFeature(features) &&
|
||||||
|
attributes.isAuthenticated() &&
|
||||||
|
attributes.getConnection().resource.isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
|
XMLNode nonza,
|
||||||
|
) async {
|
||||||
if (!_requestSent) {
|
if (!_requestSent) {
|
||||||
final stanza = XMLNode.xmlns(
|
final stanza = XMLNode.xmlns(
|
||||||
tag: 'iq',
|
tag: 'iq',
|
||||||
@@ -52,14 +68,13 @@ class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final bind = nonza.firstTag('bind')!;
|
final bind = nonza.firstTag('bind')!;
|
||||||
final jid = bind.firstTag('jid')!;
|
final rawJid = bind.firstTag('jid')!.innerText();
|
||||||
final resource = jid.innerText().split('/')[1];
|
final resource = JID.fromString(rawJid).resource;
|
||||||
|
attributes.setResource(resource);
|
||||||
await attributes.sendEvent(ResourceBindingSuccessEvent(resource: resource));
|
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void reset() {
|
void reset() {
|
||||||
_requestSent = false;
|
_requestSent = false;
|
||||||