Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
No files matched your search
@@ -13,3 +13,6 @@ pubspec.lock
|
||||
|
||||
# Omit pubspec override files generated by melos
|
||||
**/pubspec_overrides.yaml
|
||||
|
||||
# Flake results
|
||||
result
|
||||
@@ -0,0 +1,14 @@
|
||||
[general]
|
||||
ignore=B5,B6,B7,B8
|
||||
|
||||
[title-max-length]
|
||||
line-length=72
|
||||
|
||||
[title-trailing-punctuation]
|
||||
[title-hard-tab]
|
||||
[title-match-regex]
|
||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example)+(,(meta|tests|style|docs|xep|core|example))*\)|release): [A-Z0-9].*$
|
||||
|
||||
|
||||
[body-trailing-whitespace]
|
||||
[body-first-line-empty]
|
||||
@@ -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.
|
||||
@@ -7,6 +7,8 @@ moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
|
||||
|
||||
This package contains the actual XMPP code that is platform-independent.
|
||||
|
||||
Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||
|
||||
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
|
||||
|
||||
`moxxmpp_socket_tcp` contains the implementation of the `BaseSocketWrapper` class that
|
||||
@@ -22,6 +24,15 @@ To run the example, make sure that Flutter is correctly set up and working. If y
|
||||
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
|
||||
`ANDROID_AVD_HOME` are pointing to the correct directories.
|
||||
|
||||
## Examples
|
||||
|
||||
This repository contains 2 types of examples:
|
||||
|
||||
- `example_flutter`: An example of using moxxmpp using Flutter
|
||||
- `examples_dart`: A collection of pure Dart examples for showing different aspects of moxxmpp
|
||||
|
||||
For more information, see the respective README files.
|
||||
|
||||
## License
|
||||
|
||||
See `./LICENSE`.
|
||||
|
||||
@@ -11,5 +11,3 @@ analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
- "test/"
|
||||
- "integration_test/"
|
||||
@@ -1,6 +0,0 @@
|
||||
# melos_managed_dependency_overrides: moxxmpp,moxxmpp_socket_tcp
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -5,18 +5,20 @@ import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
class ExampleTcpSocketWrapper extends TCPSocketWrapper {
|
||||
ExampleTcpSocketWrapper() : super(false);
|
||||
ExampleTcpSocketWrapper() : super();
|
||||
|
||||
@override
|
||||
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
||||
final records = await MoxdnsPlugin.srvQuery(domain, false);
|
||||
return records
|
||||
.map((record) => MoxSrvRecord(
|
||||
.map(
|
||||
(record) => MoxSrvRecord(
|
||||
record.priority,
|
||||
record.weight,
|
||||
record.target,
|
||||
record.port,
|
||||
),)
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +26,7 @@ class ExampleTcpSocketWrapper extends TCPSocketWrapper {
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||
});
|
||||
|
||||
@@ -54,22 +57,32 @@ class MyHomePage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final logger = Logger('MyHomePage');
|
||||
final XmppConnection connection = XmppConnection(
|
||||
ExponentialBackoffReconnectionPolicy(),
|
||||
ExampleTcpSocketWrapper(),
|
||||
RandomBackoffReconnectionPolicy(1, 60),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
// The below causes the app to crash.
|
||||
//ExampleTcpSocketWrapper(),
|
||||
// In a production app, the below should be false.
|
||||
TCPSocketWrapper(),
|
||||
);
|
||||
TextEditingController jidController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool connected = false;
|
||||
bool loading = false;
|
||||
|
||||
_MyHomePageState() : super() {
|
||||
connection
|
||||
..registerManagers([
|
||||
StreamManagementManager(),
|
||||
DiscoManager(),
|
||||
RosterManager(),
|
||||
PingManager(),
|
||||
DiscoManager([]),
|
||||
RosterManager(TestingRosterStateManager("", [])),
|
||||
PingManager(
|
||||
const Duration(minutes: 3),
|
||||
),
|
||||
MessageManager(),
|
||||
PresenceManager('http://moxxmpp.example'),
|
||||
PresenceManager(),
|
||||
])
|
||||
..registerFeatureNegotiators([
|
||||
ResourceBindingNegotiator(),
|
||||
@@ -85,15 +98,38 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
}
|
||||
|
||||
Future<void> _buttonPressed() async {
|
||||
connection.setConnectionSettings(
|
||||
ConnectionSettings(
|
||||
if (connected) {
|
||||
await connection.disconnect();
|
||||
setState(() {
|
||||
connected = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
connection.connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString(jidController.text),
|
||||
password: passwordController.text,
|
||||
useDirectTLS: true,
|
||||
allowPlainAuth: false,
|
||||
);
|
||||
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()),
|
||||
),
|
||||
);
|
||||
await connection.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -101,20 +137,24 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
backgroundColor: connected ? Colors.green : Colors.deepPurple[800],
|
||||
foregroundColor: connected ? Colors.black : Colors.white,
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
enabled: !loading,
|
||||
controller: jidController,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'JID',
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
enabled: !loading,
|
||||
controller: passwordController,
|
||||
decoration: InputDecoration(
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Password',
|
||||
),
|
||||
obscureText: true,
|
||||
@@ -122,10 +162,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: _buttonPressed,
|
||||
label: Text(connected ? 'Disconnect' : 'Connect'),
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
tooltip: 'Connect',
|
||||
child: const Icon(Icons.add),
|
||||
icon: const Icon(Icons.power),
|
||||
),
|
||||
);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -16,10 +16,10 @@ dependencies:
|
||||
version: 0.1.4+1
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.6+1
|
||||
version: 0.3.1
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.2+9
|
||||
version: 0.3.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -0,0 +1,6 @@
|
||||
# Files and directories created by pub.
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
||||
# Conventional directory for build output.
|
||||
build/
|
||||
@@ -0,0 +1,7 @@
|
||||
# Dart Examples
|
||||
|
||||
Run using `dart run bin/<example>.dart`.
|
||||
|
||||
## Examples
|
||||
|
||||
- `component.dart`: Use moxxmpp to implement a component using XEP-0114.
|
||||
@@ -0,0 +1,30 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class EchoMessageManager extends XmppManagerBase {
|
||||
EchoMessageManager() : super('org.moxxy.example.message');
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessage,
|
||||
priority: -100,
|
||||
xmlns: null,
|
||||
)
|
||||
];
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final body = stanza.firstTag('body');
|
||||
if (body == null) return state.copyWith(done: true);
|
||||
|
||||
final bodyText = body.innerText();
|
||||
|
||||
await getAttributes().sendStanza(
|
||||
Stanza.message(
|
||||
to: stanza.from,
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'body',
|
||||
text: 'Hello, ${stanza.from}! You said "$bodyText"',
|
||||
),
|
||||
],
|
||||
),
|
||||
awaitable: false,
|
||||
);
|
||||
|
||||
return state.copyWith(done: true);
|
||||
}
|
||||
}
|
||||
|
||||
void main(List<String> arguments) async {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ComponentToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('component.localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
);
|
||||
await conn.registerManagers([
|
||||
EchoMessageManager(),
|
||||
]);
|
||||
|
||||
final result = await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
if (result.isType<XmppError>()) {
|
||||
print('Failed to connect as component');
|
||||
return;
|
||||
}
|
||||
|
||||
// Just block for some time to test the connection
|
||||
await Future<void>.delayed(const Duration(seconds: 9999));
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
name: example_dart
|
||||
description: A sample command-line application.
|
||||
version: 1.0.0
|
||||
# homepage: https://www.example.com
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
logging: ^1.0.2
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.3.1
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.3.1
|
||||
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
Generated
+24
-7
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1678901627,
|
||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -17,11 +17,27 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1667610399,
|
||||
"narHash": "sha256-XZd0f4ZWAY0QOoUSdiNWj/eFiKb4B9CJPtl9uO9SYY4=",
|
||||
"lastModified": 1676076353,
|
||||
"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",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1dd8696f96db47156e1424a49578fe7dd4ce99a4",
|
||||
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -34,7 +50,8 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
description = "moxxmpp";
|
||||
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";
|
||||
};
|
||||
|
||||
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 {
|
||||
inherit system;
|
||||
config = {
|
||||
@@ -13,6 +14,9 @@
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
unstable = import nixpkgs-unstable {
|
||||
inherit system;
|
||||
};
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
# TODO: Find a way to pin these
|
||||
#toolsVersion = "26.1.1";
|
||||
@@ -29,15 +33,49 @@
|
||||
useGoogleAPIs = false;
|
||||
useGoogleTVAddOns = false;
|
||||
};
|
||||
pinnedJDK = pkgs.jdk;
|
||||
pinnedJDK = pkgs.jdk17;
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||
pyyaml
|
||||
requests
|
||||
]);
|
||||
|
||||
moxxmppPubCache = import ./nix/pubcache.moxxmpp.nix {
|
||||
inherit (pkgs) fetchzip runCommand;
|
||||
};
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
packages = {
|
||||
moxxmppDartDocs = pkgs.callPackage ./nix/moxxmpp-docs.nix {
|
||||
inherit (moxxmppPubCache) pubCache;
|
||||
};
|
||||
};
|
||||
|
||||
devShell = let
|
||||
prosody-newer-community-modules = unstable.prosody.overrideAttrs (old: {
|
||||
communityModules = pkgs.fetchhg {
|
||||
url = "https://hg.prosody.im/prosody-modules";
|
||||
rev = "e3a3a6c86a9f";
|
||||
sha256 = "sha256-C2x6PCv0sYuj4/SroDOJLsNPzfeNCodYKbMqmNodFrk=";
|
||||
};
|
||||
|
||||
src = pkgs.fetchhg {
|
||||
url = "https://hg.prosody.im/trunk";
|
||||
rev = "8a2f75e38eb2";
|
||||
sha256 = "sha256-zMNp9+wQ/hvUVyxFl76DqCVzQUPP8GkNdstiTDkG8Hw=";
|
||||
};
|
||||
});
|
||||
prosody-sasl2 = prosody-newer-community-modules.override {
|
||||
withCommunityModules = [
|
||||
"sasl2" "sasl2_fast" "sasl2_sm" "sasl2_bind2"
|
||||
];
|
||||
};
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
flutter pinnedJDK android.platform-tools dart # Flutter/Android
|
||||
flutter pinnedJDK android.platform-tools dart # Dart
|
||||
gitlint # Code hygiene
|
||||
ripgrep # General utilities
|
||||
|
||||
# Flutter dependencies for linux desktop
|
||||
# Flutter dependencies for Linux desktop
|
||||
atk
|
||||
cairo
|
||||
clang
|
||||
@@ -53,6 +91,13 @@
|
||||
pkg-config
|
||||
xorg.libX11
|
||||
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";
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Files and directories created by pub.
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
||||
# Conventional directory for build output.
|
||||
build/
|
||||
@@ -0,0 +1,5 @@
|
||||
# Integration Tests
|
||||
|
||||
The included `./prosody.cfg.lua` config file must be used for integration testing.
|
||||
Additionally, ensure that a user `testuser@localhost` with the password `abc123`
|
||||
exists. Note that this currently requires prosody-trunk.
|
||||
@@ -0,0 +1 @@
|
||||
include: ../analysis_options.yaml
|
||||
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEAzCCAmugAwIBAgIQd61NPnP8++X7h8a+85C6DjANBgkqhkiG9w0BAQsFADBZ
|
||||
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFzAVBgNVBAsMDmFsZXhh
|
||||
bmRlckBtaWt1MR4wHAYDVQQDDBVta2NlcnQgYWxleGFuZGVyQG1pa3UwHhcNMjMw
|
||||
NDAyMTM1ODIxWhcNMjUwNzAyMTM1ODIxWjBCMScwJQYDVQQKEx5ta2NlcnQgZGV2
|
||||
ZWxvcG1lbnQgY2VydGlmaWNhdGUxFzAVBgNVBAsMDmFsZXhhbmRlckBtaWt1MIIB
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1DElEXPY+VDQP7cSikK0ne0K
|
||||
gDgorGYPG9R7lOeuPLHyFYYry78+hB037OT0BOyA2uTu1yrog0dI/4YGicPDIqXh
|
||||
IgHfjV+4kMi5SgO7ECWOBmZFqTC3bBwvbNtoW40aFjYSFaOkm/nnfp+nalEJJZ/N
|
||||
kSkD4gdT3pH1ClsovlI4BlsxeIoJtyGzxMidJVXDAqMNraLatzJBwnT3OEs93xTf
|
||||
7Kd1KUpQp9OZFrGi15zv/n6tCmrcC3xMOVHuYkhW0UCTFmev7ZqbghQsQ9N9s0E6
|
||||
kk9rUf9xtMNH4Af6+2YRkT1DAGQ6FkXl1nQdB5H5XRgOBl+3k9s8wUrxQvQddQID
|
||||
AQABo14wXDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
|
||||
VR0jBBgwFoAU54aUZ+dytAOBTsYIdGtSnjiig/gwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBgQBU8p7Ua0Cs+lXlWmtCh2j+YF9R+dvc+3Iw
|
||||
dYEzCmYd375uxPctyHXW0yYjyuH9WuYn0F7OicEFEeC2+exHND+/z0J2Zv5yu34r
|
||||
SfgHVfvE/Vxisn9InYrUCVtfRwLDF3HgLyIlm8FVzIyiIANhpe6vJdqjEWTsiL2X
|
||||
I6hoDf1xlRgEqUx+Wxl2IFWrg+1SPPGTQzDPImiRlz8d+9ZJ9v48vaV5+aITMvDP
|
||||
Gfm/bnNXXd5Gf7nGwL8zFHiwLoYQ5AUYl0IfXYwFAXJ72+LjiRT33IOidVJF0gsQ
|
||||
6k9cTsc4lIrt4FOzdchalbF1Eu2prieWoZxz0apG8OuUeAhaB+t8kT6swAkwvkLW
|
||||
OnlSATm9Cls9Pc4XDHTbZlbMmwF2Jmukgz/l1vlTutt4ZgZwQkSEa9Qfoi9Zym0R
|
||||
iKls1CgD49zguR/cFDKK3agvfv6Afw6HdgaS/WqcI/Ros7b+RCkbAlAG5gqr6BLQ
|
||||
8RGyVjZSC4Mz/ddcnMEpRAnjuFJjhGA=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUMSURc9j5UNA/
|
||||
txKKQrSd7QqAOCisZg8b1HuU5648sfIVhivLvz6EHTfs5PQE7IDa5O7XKuiDR0j/
|
||||
hgaJw8MipeEiAd+NX7iQyLlKA7sQJY4GZkWpMLdsHC9s22hbjRoWNhIVo6Sb+ed+
|
||||
n6dqUQkln82RKQPiB1PekfUKWyi+UjgGWzF4igm3IbPEyJ0lVcMCow2totq3MkHC
|
||||
dPc4Sz3fFN/sp3UpSlCn05kWsaLXnO/+fq0KatwLfEw5Ue5iSFbRQJMWZ6/tmpuC
|
||||
FCxD032zQTqST2tR/3G0w0fgB/r7ZhGRPUMAZDoWReXWdB0HkfldGA4GX7eT2zzB
|
||||
SvFC9B11AgMBAAECggEAYaj4yY6LFzxVjG2i79WBsYnOonK2bZpPa9ygwEjdTXwM
|
||||
0lE9SPoNONsFyVca5EVBjP1+27MY7orZkxlJWxCpeAHmmzNHg5bBqIlpliIfb3AJ
|
||||
bPKXLyaH1Q8n2K8m2bQYhI6ARktZ0Jv1KrcqY2lGj3V8NEovSlFbDX4ZzJlmKCly
|
||||
d4Ia6eQ7f9AjgsOwpQGeCTF7WLaVDnch6D4JfCGrW08lFeaqogiBQczsOE3hcNSd
|
||||
tEul21Z0CkC7Iiw28KdkApPINquo1VYdAcOvUCOXkwJfPC1gsJwK4O2jxfi9v5NF
|
||||
uU1niK0/00b396pQKvXpkfViynexwzK0MZCoo3zuQQKBgQDzaZexcniQNDyWqN3C
|
||||
oMe4V3rnxs+aO/lu8Ed3mng+Jf4vuarZlxNot7WRBMGT/T+b7/UIrqRJy50CYAPY
|
||||
3RRR84tLg3UMwUWhDYsPucNc2icODBG4c+QWJ300W19r+J+iT8PwS9AbH2n094Rn
|
||||
LCRYFrX5aMsgIH5uwuncKzweMQKBgQDfKj2i1ptC53aOcr1tMCFYcnMGtaAZ8u6+
|
||||
cKSgnzKlTw/g0EYlGcETUnCyZe0oVYWp3y859FBXU0JMDmxu84aYEZNF6BwRVlpF
|
||||
feQgtUFZHyf9MepQGhjIJ5El8n7jhh1bsBY18QbDFe6/GtqPx/mQEF7vE+wPFl9h
|
||||
putwdv3OhQKBgGKPyi2/BVSW4kW7IPiTM+vP+GNrnFp+mHS0dKvYb4HyzmcyzhyH
|
||||
UQOhB7Mt8thivmP9GQIn/TwoZ24zxLsGYhkA/dFY7Id6pyAcpMd8V7/8Ub4dYvuG
|
||||
acASw1709MF6jeEiXVuqxxyEbtoTc5h3Rkwo/gx8w2tB3RAqepl9JD2xAoGAfVL3
|
||||
ci8a2iOqTKza/Cp/T3BWcHonAuuOb5xKl3lPs84GmLXd7o/cAcHWUBk1aeU9Pvx7
|
||||
RQyS4bd8D8I52sUf3N5h2mxS9tmLsGLWbhfcLvR0PJh/gaRmLmEp/imEYLm8WvU0
|
||||
Q+6rYXs7rE6kVwJygBjxd0m003Q49FoM9gec2RECgYEA5SLAe2UmJSLIb0DKk27o
|
||||
nSfARDSdi9N40vIjDFHmDRdKTOYicED/f7KqXnxVpvFxDdCvJ7xeC4V7vkaqiiwd
|
||||
/oMLQq0GjmBxG/PNd1AFIWDydyH+JcY6U4XWIzIw92OKVYC/KMvd2f9orTfmDyAU
|
||||
RsGMfgV90kCzouAZKy3yPmo=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,60 @@
|
||||
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"
|
||||
@@ -0,0 +1,16 @@
|
||||
name: integration_tests
|
||||
description: A sample command-line application.
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
logging: ^1.0.2
|
||||
moxxmpp: 0.3.0
|
||||
moxxmpp_socket_tcp: 0.3.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
very_good_analysis: ^3.0.1
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
test('Test connecting to prosody as a component', () async {
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ComponentToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('component.localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
);
|
||||
|
||||
final result = await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
expect(result.isType<bool>(), true);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
test('Test authenticating against Prosody with SASL2, Bind2, and FAST',
|
||||
() async {
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('testuser@localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 5222,
|
||||
);
|
||||
final csi = CSIManager();
|
||||
await csi.setInactive(sendNonza: false);
|
||||
await conn.registerManagers([
|
||||
RosterManager(TestingRosterStateManager('', [])),
|
||||
DiscoManager([]),
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
FASTSaslNegotiator(),
|
||||
Bind2Negotiator(),
|
||||
StartTlsNegotiator(),
|
||||
Sasl2Negotiator(
|
||||
userAgent: const UserAgent(
|
||||
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
|
||||
software: 'moxxmpp',
|
||||
device: "PapaTutuWawa's awesome device",
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
final result = await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
enableReconnectOnSuccess: false,
|
||||
);
|
||||
expect(result.isType<bool>(), true);
|
||||
expect(
|
||||
conn.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)!.state,
|
||||
NegotiatorState.done,
|
||||
);
|
||||
expect(
|
||||
conn
|
||||
.getNegotiatorById<FASTSaslNegotiator>(saslFASTNegotiator)!
|
||||
.fastToken !=
|
||||
null,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
stdenv
|
||||
, pubCache
|
||||
, dart
|
||||
, lib
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
pname = "moxxmpp-docs";
|
||||
version = "0.3.1";
|
||||
|
||||
PUB_CACHE = "${pubCache}";
|
||||
|
||||
src = "${./..}/packages/moxxmpp";
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
(
|
||||
set -x
|
||||
echo $PUB_CACHE
|
||||
${dart}/bin/dart pub get --no-precompile --offline
|
||||
)
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
${dart}/bin/dart doc -o $out
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,730 @@
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
archive_url: https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5
|
||||
source: hosted
|
||||
version: 50.0.0
|
||||
analyzer:
|
||||
archive_url: https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r
|
||||
source: hosted
|
||||
version: 5.2.0
|
||||
args:
|
||||
archive_url: https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g
|
||||
source: hosted
|
||||
version: 2.3.1
|
||||
async:
|
||||
archive_url: https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx
|
||||
source: hosted
|
||||
version: 2.10.0
|
||||
boolean_selector:
|
||||
archive_url: https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj
|
||||
source: hosted
|
||||
version: 2.1.1
|
||||
build:
|
||||
archive_url: https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd
|
||||
source: hosted
|
||||
version: 2.3.1
|
||||
build_config:
|
||||
archive_url: https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj
|
||||
source: hosted
|
||||
version: 1.1.1
|
||||
build_daemon:
|
||||
archive_url: https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j
|
||||
source: hosted
|
||||
version: 3.1.0
|
||||
build_resolvers:
|
||||
archive_url: https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5
|
||||
source: hosted
|
||||
version: 2.1.0
|
||||
build_runner:
|
||||
archive_url: https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz
|
||||
dependency: direct dev
|
||||
description:
|
||||
name: build_runner
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6
|
||||
source: hosted
|
||||
version: 2.3.2
|
||||
build_runner_core:
|
||||
archive_url: https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z
|
||||
source: hosted
|
||||
version: 7.2.7
|
||||
built_collection:
|
||||
archive_url: https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3
|
||||
source: hosted
|
||||
version: 5.1.1
|
||||
built_value:
|
||||
archive_url: https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i
|
||||
source: hosted
|
||||
version: 8.4.2
|
||||
checked_yaml:
|
||||
archive_url: https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k
|
||||
source: hosted
|
||||
version: 2.0.1
|
||||
code_builder:
|
||||
archive_url: https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg
|
||||
source: hosted
|
||||
version: 4.3.0
|
||||
collection:
|
||||
archive_url: https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: collection
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545
|
||||
source: hosted
|
||||
version: 1.17.0
|
||||
convert:
|
||||
archive_url: https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri
|
||||
source: hosted
|
||||
version: 3.1.1
|
||||
coverage:
|
||||
archive_url: https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d
|
||||
source: hosted
|
||||
version: 1.6.1
|
||||
crypto:
|
||||
archive_url: https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds
|
||||
source: hosted
|
||||
version: 3.0.2
|
||||
cryptography:
|
||||
archive_url: https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: cryptography
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs
|
||||
source: hosted
|
||||
version: 2.0.5
|
||||
dart_style:
|
||||
archive_url: https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v
|
||||
source: hosted
|
||||
version: 2.2.4
|
||||
file:
|
||||
archive_url: https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7
|
||||
source: hosted
|
||||
version: 6.1.4
|
||||
fixnum:
|
||||
archive_url: https://pub.dartlang.org/packages/fixnum/versions/1.0.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk
|
||||
source: hosted
|
||||
version: 1.0.1
|
||||
freezed:
|
||||
archive_url: https://pub.dartlang.org/packages/freezed/versions/2.1.1.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: freezed
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1i9s4djf4vlz56zqn8brcck3n7sk07qay23wmaan991cqydd10iq
|
||||
source: hosted
|
||||
version: 2.1.1
|
||||
freezed_annotation:
|
||||
archive_url: https://pub.dartlang.org/packages/freezed_annotation/versions/2.1.0.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: freezed_annotation
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0ym120dh1lpfnb68gxh1finm8p9l445q5x10aw8269y469b9k9z3
|
||||
source: hosted
|
||||
version: 2.1.0
|
||||
frontend_server_client:
|
||||
archive_url: https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj
|
||||
source: hosted
|
||||
version: 3.1.0
|
||||
glob:
|
||||
archive_url: https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs
|
||||
source: hosted
|
||||
version: 2.1.0
|
||||
graphs:
|
||||
archive_url: https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9
|
||||
source: hosted
|
||||
version: 2.2.0
|
||||
hex:
|
||||
archive_url: https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: hex
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh
|
||||
source: hosted
|
||||
version: 0.2.0
|
||||
http_multi_server:
|
||||
archive_url: https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql
|
||||
source: hosted
|
||||
version: 3.2.1
|
||||
http_parser:
|
||||
archive_url: https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd
|
||||
source: hosted
|
||||
version: 4.0.2
|
||||
io:
|
||||
archive_url: https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni
|
||||
source: hosted
|
||||
version: 1.0.3
|
||||
js:
|
||||
archive_url: https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50
|
||||
source: hosted
|
||||
version: 0.6.5
|
||||
json_annotation:
|
||||
archive_url: https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby
|
||||
source: hosted
|
||||
version: 4.7.0
|
||||
json_serializable:
|
||||
archive_url: https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: json_serializable
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg
|
||||
source: hosted
|
||||
version: 6.5.4
|
||||
logging:
|
||||
archive_url: https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: logging
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs
|
||||
source: hosted
|
||||
version: 1.0.2
|
||||
matcher:
|
||||
archive_url: https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0
|
||||
source: hosted
|
||||
version: 0.12.13
|
||||
meta:
|
||||
archive_url: https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: meta
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5
|
||||
source: hosted
|
||||
version: 1.8.0
|
||||
mime:
|
||||
archive_url: https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896
|
||||
source: hosted
|
||||
version: 1.0.2
|
||||
moxlib:
|
||||
archive_url: https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: moxlib
|
||||
url: https://git.polynom.me/api/packages/Moxxy/pub/
|
||||
sha256: 1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq
|
||||
source: hosted
|
||||
version: 0.1.5
|
||||
node_preamble:
|
||||
archive_url: https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx
|
||||
source: hosted
|
||||
version: 2.0.1
|
||||
omemo_dart:
|
||||
archive_url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: omemo_dart
|
||||
url: https://git.polynom.me/api/packages/PapaTutuWawa/pub/
|
||||
sha256: 09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi
|
||||
source: hosted
|
||||
version: 0.4.3
|
||||
package_config:
|
||||
archive_url: https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc
|
||||
source: hosted
|
||||
version: 2.1.0
|
||||
path:
|
||||
archive_url: https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181
|
||||
source: hosted
|
||||
version: 1.8.2
|
||||
pedantic:
|
||||
archive_url: https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pedantic
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad
|
||||
source: hosted
|
||||
version: 1.11.1
|
||||
petitparser:
|
||||
archive_url: https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0
|
||||
source: hosted
|
||||
version: 5.1.0
|
||||
pinenacl:
|
||||
archive_url: https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pinenacl
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg
|
||||
source: hosted
|
||||
version: 0.5.1
|
||||
pool:
|
||||
archive_url: https://pub.dartlang.org/packages/pool/versions/1.5.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4
|
||||
source: hosted
|
||||
version: 1.5.1
|
||||
pub_semver:
|
||||
archive_url: https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi
|
||||
source: hosted
|
||||
version: 2.1.2
|
||||
pubspec_parse:
|
||||
archive_url: https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1
|
||||
source: hosted
|
||||
version: 1.2.1
|
||||
random_string:
|
||||
archive_url: https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: random_string
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff
|
||||
source: hosted
|
||||
version: 2.3.1
|
||||
saslprep:
|
||||
archive_url: https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: saslprep
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7
|
||||
source: hosted
|
||||
version: 1.0.2
|
||||
shelf:
|
||||
archive_url: https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1
|
||||
source: hosted
|
||||
version: 1.4.0
|
||||
shelf_packages_handler:
|
||||
archive_url: https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q
|
||||
source: hosted
|
||||
version: 3.0.1
|
||||
shelf_static:
|
||||
archive_url: https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34
|
||||
source: hosted
|
||||
version: 1.1.1
|
||||
shelf_web_socket:
|
||||
archive_url: https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn
|
||||
source: hosted
|
||||
version: 1.0.3
|
||||
source_gen:
|
||||
archive_url: https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz
|
||||
source: hosted
|
||||
version: 1.2.6
|
||||
source_helper:
|
||||
archive_url: https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd
|
||||
source: hosted
|
||||
version: 1.3.3
|
||||
source_map_stack_trace:
|
||||
archive_url: https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k
|
||||
source: hosted
|
||||
version: 2.1.1
|
||||
source_maps:
|
||||
archive_url: https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6
|
||||
source: hosted
|
||||
version: 0.10.11
|
||||
source_span:
|
||||
archive_url: https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86
|
||||
source: hosted
|
||||
version: 1.9.1
|
||||
stack_trace:
|
||||
archive_url: https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na
|
||||
source: hosted
|
||||
version: 1.11.0
|
||||
stream_channel:
|
||||
archive_url: https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32
|
||||
source: hosted
|
||||
version: 2.1.1
|
||||
stream_transform:
|
||||
archive_url: https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp
|
||||
source: hosted
|
||||
version: 2.1.0
|
||||
string_scanner:
|
||||
archive_url: https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00
|
||||
source: hosted
|
||||
version: 1.2.0
|
||||
synchronized:
|
||||
archive_url: https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: synchronized
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i
|
||||
source: hosted
|
||||
version: 3.0.0+2
|
||||
term_glyph:
|
||||
archive_url: https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9
|
||||
source: hosted
|
||||
version: 1.2.1
|
||||
test:
|
||||
archive_url: https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz
|
||||
dependency: direct dev
|
||||
description:
|
||||
name: test
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x
|
||||
source: hosted
|
||||
version: 1.22.0
|
||||
test_api:
|
||||
archive_url: https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px
|
||||
source: hosted
|
||||
version: 0.4.16
|
||||
test_core:
|
||||
archive_url: https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005
|
||||
source: hosted
|
||||
version: 0.4.20
|
||||
timing:
|
||||
archive_url: https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg
|
||||
source: hosted
|
||||
version: 1.0.0
|
||||
typed_data:
|
||||
archive_url: https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g
|
||||
source: hosted
|
||||
version: 1.3.1
|
||||
unorm_dart:
|
||||
archive_url: https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v
|
||||
source: hosted
|
||||
version: 0.2.0
|
||||
uuid:
|
||||
archive_url: https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: uuid
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg
|
||||
source: hosted
|
||||
version: 3.0.5
|
||||
very_good_analysis:
|
||||
archive_url: https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz
|
||||
dependency: direct dev
|
||||
description:
|
||||
name: very_good_analysis
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4
|
||||
source: hosted
|
||||
version: 3.1.0
|
||||
vm_service:
|
||||
archive_url: https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5
|
||||
source: hosted
|
||||
version: 9.4.0
|
||||
watcher:
|
||||
archive_url: https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg
|
||||
source: hosted
|
||||
version: 1.0.2
|
||||
web_socket_channel:
|
||||
archive_url: https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a
|
||||
source: hosted
|
||||
version: 2.2.0
|
||||
webkit_inspection_protocol:
|
||||
archive_url: https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx
|
||||
source: hosted
|
||||
version: 1.2.0
|
||||
xml:
|
||||
archive_url: https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz
|
||||
dependency: direct main
|
||||
description:
|
||||
name: xml
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01
|
||||
source: hosted
|
||||
version: 6.2.0
|
||||
yaml:
|
||||
archive_url: https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: https://pub.dartlang.org
|
||||
sha256: 0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw
|
||||
source: hosted
|
||||
version: 3.1.1
|
||||
@@ -0,0 +1,814 @@
|
||||
{fetchzip, runCommand} : rec {
|
||||
_fe_analyzer_shared = fetchzip {
|
||||
sha256 = "1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5";
|
||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
analyzer = fetchzip {
|
||||
sha256 = "0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r";
|
||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
args = fetchzip {
|
||||
sha256 = "0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g";
|
||||
url = "https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
async = fetchzip {
|
||||
sha256 = "00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx";
|
||||
url = "https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
boolean_selector = fetchzip {
|
||||
sha256 = "0hxq8072hb89q9s91xlz9fvrjxfy7hw6jkdwkph5dp77df841kmj";
|
||||
url = "https://pub.dartlang.org/packages/boolean_selector/versions/2.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build = fetchzip {
|
||||
sha256 = "1x6nkii6kqy6y7ck0151yfhc9lp2nvbhznnhdi2mxr8afk6jxigd";
|
||||
url = "https://pub.dartlang.org/packages/build/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_config = fetchzip {
|
||||
sha256 = "092rrbhbdy9fk50jqb1fwj1sfk415fi43irvsd0hk5w90gn8vazj";
|
||||
url = "https://pub.dartlang.org/packages/build_config/versions/1.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_daemon = fetchzip {
|
||||
sha256 = "0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j";
|
||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_resolvers = fetchzip {
|
||||
sha256 = "0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5";
|
||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner = fetchzip {
|
||||
sha256 = "0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6";
|
||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner_core = fetchzip {
|
||||
sha256 = "0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z";
|
||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
built_collection = fetchzip {
|
||||
sha256 = "0bqjahxr42q84w91nhv3n4cr580l3s3ffx3vgzyyypgqnrck0hv3";
|
||||
url = "https://pub.dartlang.org/packages/built_collection/versions/5.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
built_value = fetchzip {
|
||||
sha256 = "0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i";
|
||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
checked_yaml = fetchzip {
|
||||
sha256 = "1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k";
|
||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
code_builder = fetchzip {
|
||||
sha256 = "1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg";
|
||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
collection = fetchzip {
|
||||
sha256 = "1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545";
|
||||
url = "https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
convert = fetchzip {
|
||||
sha256 = "0adsigjk3l1c31i6k91p28dqyjlgwiqrs4lky5djrm2scf8k6cri";
|
||||
url = "https://pub.dartlang.org/packages/convert/versions/3.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
coverage = fetchzip {
|
||||
sha256 = "0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d";
|
||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
crypto = fetchzip {
|
||||
sha256 = "1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds";
|
||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
cryptography = fetchzip {
|
||||
sha256 = "0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs";
|
||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
dart_style = fetchzip {
|
||||
sha256 = "01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v";
|
||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
file = fetchzip {
|
||||
sha256 = "0ajcfblf8d4dicp1sgzkbrhd0b0v0d8wl70jsnf5drjck3p3ppk7";
|
||||
url = "https://pub.dartlang.org/packages/file/versions/6.1.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
fixnum = fetchzip {
|
||||
sha256 = "1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk";
|
||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
freezed = fetchzip {
|
||||
sha256 = "1i9s4djf4vlz56zqn8brcck3n7sk07qay23wmaan991cqydd10iq";
|
||||
url = "https://pub.dartlang.org/packages/freezed/versions/2.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
freezed_annotation = fetchzip {
|
||||
sha256 = "0ym120dh1lpfnb68gxh1finm8p9l445q5x10aw8269y469b9k9z3";
|
||||
url = "https://pub.dartlang.org/packages/freezed_annotation/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
frontend_server_client = fetchzip {
|
||||
sha256 = "0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj";
|
||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
glob = fetchzip {
|
||||
sha256 = "0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs";
|
||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
graphs = fetchzip {
|
||||
sha256 = "0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9";
|
||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
hex = fetchzip {
|
||||
sha256 = "19w3f90mdiy06a6kf8hlwc4jn4cxixkj106kc3g3bis27ar7smkh";
|
||||
url = "https://pub.dartlang.org/packages/hex/versions/0.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
http_multi_server = fetchzip {
|
||||
sha256 = "1zdcm04z85jahb2hs7qs85rh974kw49hffhy9cn1gfda3077dvql";
|
||||
url = "https://pub.dartlang.org/packages/http_multi_server/versions/3.2.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
http_parser = fetchzip {
|
||||
sha256 = "027c4sjkhkkx3sk1aqs6s4djb87syi9h521qpm1bf21bq3gga5jd";
|
||||
url = "https://pub.dartlang.org/packages/http_parser/versions/4.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
io = fetchzip {
|
||||
sha256 = "1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni";
|
||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
js = fetchzip {
|
||||
sha256 = "13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50";
|
||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_annotation = fetchzip {
|
||||
sha256 = "1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby";
|
||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_serializable = fetchzip {
|
||||
sha256 = "04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg";
|
||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
logging = fetchzip {
|
||||
sha256 = "0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs";
|
||||
url = "https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
matcher = fetchzip {
|
||||
sha256 = "0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0";
|
||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
meta = fetchzip {
|
||||
sha256 = "01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5";
|
||||
url = "https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
mime = fetchzip {
|
||||
sha256 = "1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896";
|
||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
moxlib = fetchzip {
|
||||
sha256 = "1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq";
|
||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
node_preamble = fetchzip {
|
||||
sha256 = "0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx";
|
||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
omemo_dart = fetchzip {
|
||||
sha256 = "09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi";
|
||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
package_config = fetchzip {
|
||||
sha256 = "1d4l0i4cby344zj45f5shrg2pkw1i1jn03kx0qqh0l7gh1ha7bpc";
|
||||
url = "https://pub.dartlang.org/packages/package_config/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
path = fetchzip {
|
||||
sha256 = "16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181";
|
||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pedantic = fetchzip {
|
||||
sha256 = "10ch0h3hi6cfwiz2ihfkh6m36m75c0m7fd0wwqaqggffsj2dn8ad";
|
||||
url = "https://pub.dartlang.org/packages/pedantic/versions/1.11.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
petitparser = fetchzip {
|
||||
sha256 = "1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0";
|
||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pinenacl = fetchzip {
|
||||
sha256 = "0didjgva658z90hbcmhd0y8w1b8v86dp6gabfhylnw1aixl47cxg";
|
||||
url = "https://pub.dartlang.org/packages/pinenacl/versions/0.5.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pool = fetchzip {
|
||||
sha256 = "0wmzs46hjszv3ayhr1p5l7xza7q9rkg2q9z4swmhdqmhlz3c50x4";
|
||||
url = "https://pub.dartlang.org/packages/pool/versions/1.5.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pub_semver = fetchzip {
|
||||
sha256 = "1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi";
|
||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubspec_parse = fetchzip {
|
||||
sha256 = "19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1";
|
||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
random_string = fetchzip {
|
||||
sha256 = "11cjiv75sgldvk3x7w6j77lgi08r6737wm94m3ylabylsr6zdyff";
|
||||
url = "https://pub.dartlang.org/packages/random_string/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
saslprep = fetchzip {
|
||||
sha256 = "04lss0xvm6p801p8306jdxg7k0b28kr6n65dz2f57dkca237kcw7";
|
||||
url = "https://pub.dartlang.org/packages/saslprep/versions/1.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf = fetchzip {
|
||||
sha256 = "0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1";
|
||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_packages_handler = fetchzip {
|
||||
sha256 = "199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q";
|
||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_static = fetchzip {
|
||||
sha256 = "1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34";
|
||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_web_socket = fetchzip {
|
||||
sha256 = "0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn";
|
||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_gen = fetchzip {
|
||||
sha256 = "1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz";
|
||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_helper = fetchzip {
|
||||
sha256 = "044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd";
|
||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_map_stack_trace = fetchzip {
|
||||
sha256 = "0b5d4c5n5qd3j8n10gp1khhr508wfl3819bhk6xnl34qxz8n032k";
|
||||
url = "https://pub.dartlang.org/packages/source_map_stack_trace/versions/2.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_maps = fetchzip {
|
||||
sha256 = "18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6";
|
||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_span = fetchzip {
|
||||
sha256 = "1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86";
|
||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stack_trace = fetchzip {
|
||||
sha256 = "0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na";
|
||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stream_channel = fetchzip {
|
||||
sha256 = "054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32";
|
||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stream_transform = fetchzip {
|
||||
sha256 = "0jq6767v9ds17i2nd6mdd9i0f7nvsgg3dz74d0v54x66axjgr0gp";
|
||||
url = "https://pub.dartlang.org/packages/stream_transform/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
string_scanner = fetchzip {
|
||||
sha256 = "0p1r0v2923avwfg03rk0pmc6f21m0zxpcx6i57xygd25k6hdfi00";
|
||||
url = "https://pub.dartlang.org/packages/string_scanner/versions/1.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
synchronized = fetchzip {
|
||||
sha256 = "1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i";
|
||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
term_glyph = fetchzip {
|
||||
sha256 = "1x8nspxaccls0sxjamp703yp55yxdvhj6wg21lzwd296i9rwlxh9";
|
||||
url = "https://pub.dartlang.org/packages/term_glyph/versions/1.2.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test = fetchzip {
|
||||
sha256 = "08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x";
|
||||
url = "https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_api = fetchzip {
|
||||
sha256 = "0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px";
|
||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_core = fetchzip {
|
||||
sha256 = "1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005";
|
||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
timing = fetchzip {
|
||||
sha256 = "0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg";
|
||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
typed_data = fetchzip {
|
||||
sha256 = "1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g";
|
||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
unorm_dart = fetchzip {
|
||||
sha256 = "05kyk2764yz14pzgx00i7h5b1lzh8kjqnxspfzyf8z920bcgbz0v";
|
||||
url = "https://pub.dartlang.org/packages/unorm_dart/versions/0.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
uuid = fetchzip {
|
||||
sha256 = "12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg";
|
||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
very_good_analysis = fetchzip {
|
||||
sha256 = "1p2dh8aahbqyyqfzbsxswafgxnmxgisjq2xfp008skyh7imk6sz4";
|
||||
url = "https://pub.dartlang.org/packages/very_good_analysis/versions/3.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
vm_service = fetchzip {
|
||||
sha256 = "05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5";
|
||||
url = "https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
watcher = fetchzip {
|
||||
sha256 = "1sk7gvwa7s0h4l652qrgbh7l8wyqc6nr6lki8m4rj55720p0fnyg";
|
||||
url = "https://pub.dartlang.org/packages/watcher/versions/1.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
web_socket_channel = fetchzip {
|
||||
sha256 = "147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a";
|
||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
webkit_inspection_protocol = fetchzip {
|
||||
sha256 = "0z400dzw7gf68a3wm95xi2mf461iigkyq6x69xgi7qs3fvpmn3hx";
|
||||
url = "https://pub.dartlang.org/packages/webkit_inspection_protocol/versions/1.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
xml = fetchzip {
|
||||
sha256 = "0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01";
|
||||
url = "https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
yaml = fetchzip {
|
||||
sha256 = "0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw";
|
||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubCache = runCommand "moxxmpp-pub-cache" {} ''
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dartlang.org/_fe_analyzer_shared-50.0.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${analyzer} $out/hosted/pub.dartlang.org/analyzer-5.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${args} $out/hosted/pub.dartlang.org/args-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${async} $out/hosted/pub.dartlang.org/async-2.10.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${boolean_selector} $out/hosted/pub.dartlang.org/boolean_selector-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build} $out/hosted/pub.dartlang.org/build-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_config} $out/hosted/pub.dartlang.org/build_config-1.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_daemon} $out/hosted/pub.dartlang.org/build_daemon-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_resolvers} $out/hosted/pub.dartlang.org/build_resolvers-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner} $out/hosted/pub.dartlang.org/build_runner-2.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner_core} $out/hosted/pub.dartlang.org/build_runner_core-7.2.7
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_collection} $out/hosted/pub.dartlang.org/built_collection-5.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_value} $out/hosted/pub.dartlang.org/built_value-8.4.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${checked_yaml} $out/hosted/pub.dartlang.org/checked_yaml-2.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${code_builder} $out/hosted/pub.dartlang.org/code_builder-4.3.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${collection} $out/hosted/pub.dartlang.org/collection-1.17.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${convert} $out/hosted/pub.dartlang.org/convert-3.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${coverage} $out/hosted/pub.dartlang.org/coverage-1.6.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${crypto} $out/hosted/pub.dartlang.org/crypto-3.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${cryptography} $out/hosted/pub.dartlang.org/cryptography-2.0.5
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${dart_style} $out/hosted/pub.dartlang.org/dart_style-2.2.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${file} $out/hosted/pub.dartlang.org/file-6.1.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${fixnum} $out/hosted/pub.dartlang.org/fixnum-1.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed} $out/hosted/pub.dartlang.org/freezed-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed_annotation} $out/hosted/pub.dartlang.org/freezed_annotation-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${frontend_server_client} $out/hosted/pub.dartlang.org/frontend_server_client-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${glob} $out/hosted/pub.dartlang.org/glob-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${graphs} $out/hosted/pub.dartlang.org/graphs-2.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${hex} $out/hosted/pub.dartlang.org/hex-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_multi_server} $out/hosted/pub.dartlang.org/http_multi_server-3.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_parser} $out/hosted/pub.dartlang.org/http_parser-4.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${io} $out/hosted/pub.dartlang.org/io-1.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${js} $out/hosted/pub.dartlang.org/js-0.6.5
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_annotation} $out/hosted/pub.dartlang.org/json_annotation-4.7.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_serializable} $out/hosted/pub.dartlang.org/json_serializable-6.5.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${logging} $out/hosted/pub.dartlang.org/logging-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${matcher} $out/hosted/pub.dartlang.org/matcher-0.12.13
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${meta} $out/hosted/pub.dartlang.org/meta-1.8.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${mime} $out/hosted/pub.dartlang.org/mime-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47
|
||||
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.1.5
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${node_preamble} $out/hosted/pub.dartlang.org/node_preamble-2.0.1
|
||||
|
||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47
|
||||
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.4.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${package_config} $out/hosted/pub.dartlang.org/package_config-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${path} $out/hosted/pub.dartlang.org/path-1.8.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pedantic} $out/hosted/pub.dartlang.org/pedantic-1.11.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${petitparser} $out/hosted/pub.dartlang.org/petitparser-5.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pinenacl} $out/hosted/pub.dartlang.org/pinenacl-0.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pool} $out/hosted/pub.dartlang.org/pool-1.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pub_semver} $out/hosted/pub.dartlang.org/pub_semver-2.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pubspec_parse} $out/hosted/pub.dartlang.org/pubspec_parse-1.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${random_string} $out/hosted/pub.dartlang.org/random_string-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${saslprep} $out/hosted/pub.dartlang.org/saslprep-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf} $out/hosted/pub.dartlang.org/shelf-1.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dartlang.org/shelf_packages_handler-3.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_static} $out/hosted/pub.dartlang.org/shelf_static-1.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_web_socket} $out/hosted/pub.dartlang.org/shelf_web_socket-1.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_gen} $out/hosted/pub.dartlang.org/source_gen-1.2.6
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_helper} $out/hosted/pub.dartlang.org/source_helper-1.3.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dartlang.org/source_map_stack_trace-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_maps} $out/hosted/pub.dartlang.org/source_maps-0.10.11
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_span} $out/hosted/pub.dartlang.org/source_span-1.9.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stack_trace} $out/hosted/pub.dartlang.org/stack_trace-1.11.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_channel} $out/hosted/pub.dartlang.org/stream_channel-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_transform} $out/hosted/pub.dartlang.org/stream_transform-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${string_scanner} $out/hosted/pub.dartlang.org/string_scanner-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${synchronized} $out/hosted/pub.dartlang.org/synchronized-3.0.0+2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${term_glyph} $out/hosted/pub.dartlang.org/term_glyph-1.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test} $out/hosted/pub.dartlang.org/test-1.22.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_api} $out/hosted/pub.dartlang.org/test_api-0.4.16
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_core} $out/hosted/pub.dartlang.org/test_core-0.4.20
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${timing} $out/hosted/pub.dartlang.org/timing-1.0.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${typed_data} $out/hosted/pub.dartlang.org/typed_data-1.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${unorm_dart} $out/hosted/pub.dartlang.org/unorm_dart-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${uuid} $out/hosted/pub.dartlang.org/uuid-3.0.5
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${very_good_analysis} $out/hosted/pub.dartlang.org/very_good_analysis-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${vm_service} $out/hosted/pub.dartlang.org/vm_service-9.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${watcher} $out/hosted/pub.dartlang.org/watcher-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${web_socket_channel} $out/hosted/pub.dartlang.org/web_socket_channel-2.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dartlang.org/webkit_inspection_protocol-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${xml} $out/hosted/pub.dartlang.org/xml-6.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${yaml} $out/hosted/pub.dartlang.org/yaml-3.1.1
|
||||
'';
|
||||
|
||||
}
|
||||
@@ -1,3 +1,23 @@
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -9,9 +9,11 @@ Include the following as a dependency in your pubspec file:
|
||||
```
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.6+1
|
||||
version: 0.3.1
|
||||
```
|
||||
|
||||
You can find the documentation [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||
|
||||
## License
|
||||
|
||||
See `./LICENSE`.
|
||||
|
||||
@@ -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,9 +1,13 @@
|
||||
library moxxmpp;
|
||||
|
||||
export 'package:moxxmpp/src/connection.dart';
|
||||
export 'package:moxxmpp/src/connection_errors.dart';
|
||||
export 'package:moxxmpp/src/connectivity.dart';
|
||||
export 'package:moxxmpp/src/errors.dart';
|
||||
export 'package:moxxmpp/src/events.dart';
|
||||
export 'package:moxxmpp/src/handlers/base.dart';
|
||||
export 'package:moxxmpp/src/handlers/client.dart';
|
||||
export 'package:moxxmpp/src/handlers/component.dart';
|
||||
export 'package:moxxmpp/src/iq.dart';
|
||||
export 'package:moxxmpp/src/jid.dart';
|
||||
export 'package:moxxmpp/src/managers/attributes.dart';
|
||||
@@ -17,17 +21,17 @@ export 'package:moxxmpp/src/namespaces.dart';
|
||||
export 'package:moxxmpp/src/negotiators/manager.dart';
|
||||
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
export 'package:moxxmpp/src/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/presence.dart';
|
||||
export 'package:moxxmpp/src/reconnect.dart';
|
||||
export 'package:moxxmpp/src/rfcs/rfc_2782.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/roster.dart';
|
||||
export 'package:moxxmpp/src/roster/state.dart';
|
||||
@@ -37,6 +41,7 @@ export 'package:moxxmpp/src/stanza.dart';
|
||||
export 'package:moxxmpp/src/stringxml.dart';
|
||||
export 'package:moxxmpp/src/types/result.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
@@ -75,6 +80,11 @@ 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/xep_0384.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0385.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_0414.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0424.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0444.dart';
|
||||
|
||||
@@ -23,7 +23,7 @@ class _StanzaSurrogateKey {
|
||||
int get hashCode => sentTo.hashCode ^ id.hashCode ^ tag.hashCode;
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
bool operator ==(Object other) {
|
||||
return other is _StanzaSurrogateKey &&
|
||||
other.sentTo == sentTo &&
|
||||
other.id == id &&
|
||||
|
||||
@@ -1,24 +1,76 @@
|
||||
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> {
|
||||
/// A result object for XmlStreamBuffer.
|
||||
abstract class XmlStreamBufferObject {}
|
||||
|
||||
XmlStreamBuffer() : _streamController = StreamController(), _decoder = const XmlNodeDecoder();
|
||||
final StreamController<XMLNode> _streamController;
|
||||
final XmlNodeDecoder _decoder;
|
||||
/// A complete XML element returned by the stream buffer.
|
||||
class XmlStreamBufferElement extends XmlStreamBufferObject {
|
||||
XmlStreamBufferElement(this.node);
|
||||
|
||||
/// The actual [XMLNode].
|
||||
final XMLNode node;
|
||||
}
|
||||
|
||||
/// Just the stream header of a new XML stream.
|
||||
class XmlStreamBufferHeader extends XmlStreamBufferObject {
|
||||
XmlStreamBufferHeader(this.attributes);
|
||||
|
||||
/// The headers of the stream header.
|
||||
final Map<String, String> attributes;
|
||||
}
|
||||
|
||||
/// A buffer to put between a socket's input and a full XML stream.
|
||||
class XmlStreamBuffer
|
||||
extends StreamTransformerBase<String, XmlStreamBufferObject> {
|
||||
final StreamController<XmlStreamBufferObject> _streamController =
|
||||
StreamController<XmlStreamBufferObject>();
|
||||
|
||||
@override
|
||||
Stream<XMLNode> bind(Stream<String> stream) {
|
||||
stream.toXmlEvents().selectSubtreeEvents((event) {
|
||||
Stream<XmlStreamBufferObject> bind(Stream<String> stream) {
|
||||
final events = stream.toXmlEvents().asBroadcastStream();
|
||||
events.transform(
|
||||
StreamTransformer<List<XmlEvent>, XmlStartElementEvent>.fromHandlers(
|
||||
handleData: (events, sink) {
|
||||
for (final event in events) {
|
||||
if (event is! XmlStartElementEvent) {
|
||||
continue;
|
||||
}
|
||||
if (event.name != 'stream:stream') {
|
||||
continue;
|
||||
}
|
||||
|
||||
sink.add(event);
|
||||
}
|
||||
},
|
||||
),
|
||||
).listen((event) {
|
||||
_streamController.add(
|
||||
XmlStreamBufferHeader(
|
||||
Map<String, String>.fromEntries(
|
||||
event.attributes.map((attr) {
|
||||
return MapEntry(attr.name, attr.value);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
events
|
||||
.selectSubtreeEvents((event) {
|
||||
return event.qualifiedName != 'stream:stream';
|
||||
}).transform(_decoder).listen((nodes) {
|
||||
})
|
||||
.transform(const XmlNodeDecoder())
|
||||
.listen((nodes) {
|
||||
for (final node in nodes) {
|
||||
if (node.nodeType == XmlNodeType.ELEMENT) {
|
||||
_streamController.add(XMLNode.fromXmlElement(node as XmlElement));
|
||||
_streamController.add(
|
||||
XmlStreamBufferElement(
|
||||
XMLNode.fromXmlElement(node as XmlElement),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,60 @@
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
|
||||
/// The reason a call to `XmppConnection.connect` failed.
|
||||
abstract class XmppConnectionError extends XmppError {}
|
||||
|
||||
/// Returned by `XmppConnection.connect` when a negotiator returned an unrecoverable
|
||||
/// error. Only returned when waitUntilLogin is true.
|
||||
class NegotiatorReturnedError extends XmppConnectionError {
|
||||
NegotiatorReturnedError(this.error);
|
||||
|
||||
@override
|
||||
bool isRecoverable() => error.isRecoverable();
|
||||
|
||||
/// The error returned by the negotiator.
|
||||
final NegotiatorError error;
|
||||
}
|
||||
|
||||
class StreamFailureError extends XmppConnectionError {
|
||||
StreamFailureError(this.error);
|
||||
|
||||
@override
|
||||
bool isRecoverable() => error.isRecoverable();
|
||||
|
||||
/// The error that causes a connection failure.
|
||||
final XmppError error;
|
||||
}
|
||||
|
||||
/// Returned by `XmppConnection.connect` when no connection could
|
||||
/// be established.
|
||||
class NoConnectionPossibleError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if no matching authentication mechanism has been presented
|
||||
class NoMatchingAuthenticationMechanismAvailableError
|
||||
extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Returned if no negotiator was picked, even though negotiations are not done
|
||||
/// yet.
|
||||
class NoAuthenticatorAvailableError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Returned by the negotiation handler if unexpected data has been received
|
||||
class UnexpectedDataError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Returned by the ComponentToServerNegotiator if the handshake is not successful.
|
||||
class InvalidHandshakeCredentialsError extends XmppConnectionError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
@@ -1,20 +1,37 @@
|
||||
import 'package:moxxmpp/src/socket.dart';
|
||||
|
||||
/// An internal error class
|
||||
abstract class XmppError {}
|
||||
// ignore: one_member_abstracts
|
||||
abstract class XmppError {
|
||||
/// Return true if we can recover from the error by attempting a reconnection.
|
||||
bool isRecoverable();
|
||||
}
|
||||
|
||||
/// Returned if we could not establish a TCP connection
|
||||
/// to the server.
|
||||
class NoConnectionError extends XmppError {}
|
||||
class NoConnectionError extends XmppError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if a socket error occured
|
||||
class SocketError extends XmppError {
|
||||
SocketError(this.event);
|
||||
final XmppSocketErrorEvent event;
|
||||
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// Returned if we time out
|
||||
class TimeoutError extends XmppError {}
|
||||
class TimeoutError extends XmppError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
|
||||
/// 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,4 +1,5 @@
|
||||
import 'package:moxxmpp/src/connection.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/roster/roster.dart';
|
||||
@@ -25,11 +26,17 @@ class ConnectionStateChangedEvent extends XmppEvent {
|
||||
final XmppConnectionState before;
|
||||
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.
|
||||
class StreamErrorEvent extends XmppEvent {
|
||||
StreamErrorEvent({ required this.error });
|
||||
StreamErrorEvent({required this.error});
|
||||
final String error;
|
||||
}
|
||||
|
||||
@@ -42,12 +49,9 @@ class AuthenticationFailedEvent extends XmppEvent {
|
||||
/// Triggered after the SASL authentication has succeeded.
|
||||
class AuthenticationSuccessEvent extends XmppEvent {}
|
||||
|
||||
/// Triggered when we want to ping the connection open
|
||||
class SendPingEvent extends XmppEvent {}
|
||||
|
||||
/// Triggered when the stream resumption was successful
|
||||
class StreamResumedEvent extends XmppEvent {
|
||||
StreamResumedEvent({ required this.h });
|
||||
StreamResumedEvent({required this.h});
|
||||
final int h;
|
||||
}
|
||||
|
||||
@@ -127,7 +131,7 @@ class MessageEvent extends XmppEvent {
|
||||
|
||||
/// Triggered when a client responds to our delivery receipt request
|
||||
class DeliveryReceiptReceivedEvent extends XmppEvent {
|
||||
DeliveryReceiptReceivedEvent({ required this.from, required this.id });
|
||||
DeliveryReceiptReceivedEvent({required this.from, required this.id});
|
||||
final JID from;
|
||||
final String id;
|
||||
}
|
||||
@@ -156,8 +160,10 @@ class StreamManagementEnabledEvent extends XmppEvent {
|
||||
}
|
||||
|
||||
/// Triggered when we bound a resource
|
||||
class ResourceBindingSuccessEvent extends XmppEvent {
|
||||
ResourceBindingSuccessEvent({ required this.resource });
|
||||
class ResourceBoundEvent extends XmppEvent {
|
||||
ResourceBoundEvent(this.resource);
|
||||
|
||||
/// The resource that was just bound.
|
||||
final String resource;
|
||||
}
|
||||
|
||||
@@ -181,13 +187,17 @@ class ServerItemDiscoEvent extends XmppEvent {
|
||||
|
||||
/// Triggered when we receive a subscription request
|
||||
class SubscriptionRequestReceivedEvent extends XmppEvent {
|
||||
SubscriptionRequestReceivedEvent({ required this.from });
|
||||
SubscriptionRequestReceivedEvent({required this.from});
|
||||
final JID from;
|
||||
}
|
||||
|
||||
/// Triggered when we receive a new or updated avatar
|
||||
class AvatarUpdatedEvent extends XmppEvent {
|
||||
AvatarUpdatedEvent({ required this.jid, required this.base64, required this.hash });
|
||||
AvatarUpdatedEvent({
|
||||
required this.jid,
|
||||
required this.base64,
|
||||
required this.hash,
|
||||
});
|
||||
final String jid;
|
||||
final String base64;
|
||||
final String hash;
|
||||
@@ -195,7 +205,7 @@ class AvatarUpdatedEvent extends XmppEvent {
|
||||
|
||||
/// Triggered when a PubSub notification has been received
|
||||
class PubSubNotificationEvent extends XmppEvent {
|
||||
PubSubNotificationEvent({ required this.item, required this.from });
|
||||
PubSubNotificationEvent({required this.item, required this.from});
|
||||
final PubSubItem item;
|
||||
final String from;
|
||||
}
|
||||
@@ -208,13 +218,13 @@ class StanzaAckedEvent extends XmppEvent {
|
||||
|
||||
/// Triggered when receiving a push of the blocklist
|
||||
class BlocklistBlockPushEvent extends XmppEvent {
|
||||
BlocklistBlockPushEvent({ required this.items });
|
||||
BlocklistBlockPushEvent({required this.items});
|
||||
final List<String> items;
|
||||
}
|
||||
|
||||
/// Triggered when receiving a push of the blocklist
|
||||
class BlocklistUnblockPushEvent extends XmppEvent {
|
||||
BlocklistUnblockPushEvent({ required this.items });
|
||||
BlocklistUnblockPushEvent({required this.items});
|
||||
final List<String> items;
|
||||
}
|
||||
|
||||
@@ -236,3 +246,15 @@ class OmemoDeviceListUpdatedEvent extends XmppEvent {
|
||||
final JID jid;
|
||||
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 {}
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/buffer.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.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();
|
||||
|
||||
/// 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;
|
||||
|
||||
/// 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,
|
||||
) {
|
||||
this.onNegotiationsDone = onNegotiationsDone;
|
||||
this.handleError = handleError;
|
||||
this.isAuthenticated = isAuthenticated;
|
||||
this.sendNonza = sendNonza;
|
||||
this.getConnectionSettings = getConnectionSettings;
|
||||
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(XmlStreamBufferObject event) async {
|
||||
if (event is XmlStreamBufferHeader) {
|
||||
streamId = event.attributes['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/buffer.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/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() {
|
||||
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(XmlStreamBufferObject event) async {
|
||||
if (event is XmlStreamBufferElement) {
|
||||
if (event.node.tag == 'stream:features') {
|
||||
// Store the received stream features
|
||||
_streamFeatures
|
||||
..clear()
|
||||
..addAll(event.node.children);
|
||||
}
|
||||
|
||||
await _executeCurrentNegotiator(event.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:moxxmpp/src/buffer.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/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() {
|
||||
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(XmlStreamBufferObject event) async {
|
||||
switch (_state) {
|
||||
case ComponentToServerState.idle:
|
||||
if (event is XmlStreamBufferHeader) {
|
||||
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 XmlStreamBufferElement) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import 'package:moxxmpp/src/stanza.dart';
|
||||
/// Bounce a stanza if it was not handled by any manager. [conn] is the connection object
|
||||
/// to use for sending the stanza. [data] is the StanzaHandlerData of the unhandled
|
||||
/// stanza.
|
||||
Future<void> handleUnhandledStanza(XmppConnection conn, StanzaHandlerData data) async {
|
||||
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,
|
||||
|
||||
@@ -18,7 +18,10 @@ class JID {
|
||||
} else {
|
||||
resourcePart = slashParts.sublist(1).join('/');
|
||||
|
||||
assert(resourcePart.isNotEmpty, 'Resource part cannot be there and empty');
|
||||
assert(
|
||||
resourcePart.isNotEmpty,
|
||||
'Resource part cannot be there and empty',
|
||||
);
|
||||
}
|
||||
|
||||
final atParts = slashParts.first.split('@');
|
||||
@@ -34,9 +37,9 @@ class JID {
|
||||
|
||||
return JID(
|
||||
localPart,
|
||||
domainPart.endsWith('.') ?
|
||||
domainPart.substring(0, domainPart.length - 1) :
|
||||
domainPart,
|
||||
domainPart.endsWith('.')
|
||||
? domainPart.substring(0, domainPart.length - 1)
|
||||
: domainPart,
|
||||
resourcePart,
|
||||
);
|
||||
}
|
||||
@@ -63,7 +66,7 @@ class JID {
|
||||
/// 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 }) {
|
||||
bool bareCompare(JID other, {bool ensureBare = false}) {
|
||||
if (ensureBare && !other.isBare()) return false;
|
||||
|
||||
return local == other.local && domain == other.domain;
|
||||
@@ -90,7 +93,9 @@ class JID {
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
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;
|
||||
|
||||
@@ -16,14 +16,21 @@ class XmppManagerAttributes {
|
||||
required this.getManagerById,
|
||||
required this.sendEvent,
|
||||
required this.getConnectionSettings,
|
||||
required this.isFeatureSupported,
|
||||
required this.getFullJID,
|
||||
required this.getSocket,
|
||||
required this.getConnection,
|
||||
required this.getNegotiatorById,
|
||||
});
|
||||
|
||||
/// Send a stanza whose response can be awaited.
|
||||
final Future<XMLNode> Function(Stanza stanza, { StanzaFromType addFrom, bool addId, bool awaitable, bool encrypted, bool forceEncryption}) sendStanza;
|
||||
final Future<XMLNode> Function(
|
||||
Stanza stanza, {
|
||||
StanzaFromType addFrom,
|
||||
bool addId,
|
||||
bool awaitable,
|
||||
bool encrypted,
|
||||
bool forceEncryption,
|
||||
}) sendStanza;
|
||||
|
||||
/// Send a nonza.
|
||||
final void Function(XMLNode) sendNonza;
|
||||
@@ -37,9 +44,6 @@ class XmppManagerAttributes {
|
||||
/// (Maybe) Get a Manager attached to the connection by its Id.
|
||||
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
|
||||
final JID Function() getFullJID;
|
||||
|
||||
@@ -49,5 +53,6 @@ class XmppManagerAttributes {
|
||||
/// Return the [XmppConnection] the manager is registered against.
|
||||
final XmppConnection Function() getConnection;
|
||||
|
||||
final T? Function<T extends XmppFeatureNegotiatorBase>(String) getNegotiatorById;
|
||||
final T? Function<T extends XmppFeatureNegotiatorBase>(String)
|
||||
getNegotiatorById;
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/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 {
|
||||
XmppManagerBase(this.id);
|
||||
@@ -30,6 +32,29 @@ abstract class XmppManagerBase {
|
||||
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
|
||||
/// send. These are run before the stanza is sent. The higher the value of the
|
||||
/// handler's priority, the earlier it is run.
|
||||
@@ -98,25 +123,39 @@ abstract class XmppManagerBase {
|
||||
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
|
||||
Future<bool> runNonzaHandlers(XMLNode nonza) async {
|
||||
var handled = false;
|
||||
await Future.forEach(
|
||||
getNonzaHandlers(),
|
||||
(NonzaHandler handler) async {
|
||||
await Future.forEach(getNonzaHandlers(), (NonzaHandler handler) async {
|
||||
if (handler.matches(nonza)) {
|
||||
handled = true;
|
||||
await handler.callback(nonza);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
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');
|
||||
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,
|
||||
|
||||
@@ -27,8 +27,7 @@ class StanzaHandlerData with _$StanzaHandlerData {
|
||||
dynamic cancelReason,
|
||||
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
||||
// necessary, e.g. with Message Carbons or OMEMO
|
||||
Stanza stanza,
|
||||
{
|
||||
Stanza stanza, {
|
||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
||||
// stanza handlers. MUST NOT be overwritten.
|
||||
@Default(false) bool retransmitted,
|
||||
@@ -71,6 +70,5 @@ class StanzaHandlerData with _$StanzaHandlerData {
|
||||
MessageReactions? messageReactions,
|
||||
// The Id of the sticker pack this sticker belongs to
|
||||
String? stickerPackId,
|
||||
}
|
||||
) = _StanzaHandlerData;
|
||||
}) = _StanzaHandlerData;
|
||||
}
|
||||
@@ -4,87 +4,111 @@ import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// A Handler is responsible for matching any kind of toplevel item in the XML stream
|
||||
/// (stanzas and Nonzas). For that, its [matches] method is called. What happens
|
||||
/// next depends on the subclass.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class Handler {
|
||||
const Handler(this.matchStanzas, { this.nonzaTag, this.nonzaXmlns });
|
||||
final String? nonzaTag;
|
||||
final String? nonzaXmlns;
|
||||
final bool matchStanzas;
|
||||
|
||||
/// Returns true if the node matches the description provided by this [Handler].
|
||||
bool matches(XMLNode node);
|
||||
}
|
||||
|
||||
/// A Handler that specialises in matching Nonzas (and stanzas).
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
this.nonzaTag,
|
||||
this.nonzaXmlns,
|
||||
});
|
||||
|
||||
/// The function to call when a nonza matches the description.
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
|
||||
/// The expected tag of a matching nonza.
|
||||
final String? nonzaTag;
|
||||
|
||||
// The expected xmlns attribute of a matching nonza.
|
||||
final String? nonzaXmlns;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = false;
|
||||
|
||||
var matches = true;
|
||||
if (nonzaTag == null && nonzaXmlns == null) {
|
||||
matches = true;
|
||||
return true;
|
||||
} else {
|
||||
if (nonzaXmlns != null) {
|
||||
matches &= node.attributes['xmlns'] == nonzaXmlns;
|
||||
}
|
||||
|
||||
if (nonzaXmlns != null && nonzaTag != null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
String? nonzaTag,
|
||||
String? nonzaXmlns,
|
||||
}) : super(
|
||||
false,
|
||||
nonzaTag: nonzaTag,
|
||||
nonzaXmlns: nonzaXmlns,
|
||||
);
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
}
|
||||
|
||||
/// A Handler that only matches stanzas.
|
||||
class StanzaHandler extends Handler {
|
||||
StanzaHandler({
|
||||
required this.callback,
|
||||
this.tagXmlns,
|
||||
this.tagName,
|
||||
this.priority = 0,
|
||||
String? stanzaTag,
|
||||
}) : super(
|
||||
true,
|
||||
nonzaTag: stanzaTag,
|
||||
nonzaXmlns: stanzaXmlns,
|
||||
);
|
||||
this.stanzaTag,
|
||||
this.xmlns = stanzaXmlns,
|
||||
});
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a tag equal to
|
||||
/// [tagName].
|
||||
final String? tagName;
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a xmlns attribute
|
||||
/// equal to [tagXmlns]. If [tagName] is also non-null, then the element must also
|
||||
/// have a tag equal to [tagName].
|
||||
final String? tagXmlns;
|
||||
|
||||
/// If specified, the matching stanza must have a tag equal to [stanzaTag].
|
||||
final String? stanzaTag;
|
||||
|
||||
/// If specified, then the stanza must have a xmlns attribute equal to [xmlns].
|
||||
/// This defaults to [stanzaXmlns], but can be set to any other value or null. This
|
||||
/// is useful, for example, for components.
|
||||
final String? xmlns;
|
||||
|
||||
/// The priority after which [StanzaHandler]s are sorted.
|
||||
final int priority;
|
||||
|
||||
/// The function to call when a stanza matches the description.
|
||||
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = super.matches(node);
|
||||
|
||||
if (matches == false) {
|
||||
return false;
|
||||
var matches = ['iq', 'message', 'presence'].contains(node.tag);
|
||||
if (stanzaTag != null) {
|
||||
matches &= node.tag == stanzaTag;
|
||||
}
|
||||
if (xmlns != null) {
|
||||
matches &= node.xmlns == xmlns;
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
|
||||
matches &= firstTag != null;
|
||||
|
||||
matches = firstTag != null;
|
||||
} else if (tagXmlns != null) {
|
||||
return listContains(
|
||||
node.children,
|
||||
(XMLNode node_) => node_.attributes.containsKey('xmlns') && node_.attributes['xmlns'] == tagXmlns,
|
||||
);
|
||||
if (tagXmlns != null) {
|
||||
matches &= firstTag?.xmlns == tagXmlns;
|
||||
}
|
||||
|
||||
if (tagName == null && tagXmlns == null) {
|
||||
matches = true;
|
||||
} else if (tagXmlns != null) {
|
||||
matches &= listContains(
|
||||
node.children,
|
||||
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||
);
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) => b.priority.compareTo(a.priority);
|
||||
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) =>
|
||||
b.priority.compareTo(a.priority);
|
||||
@@ -10,7 +10,8 @@ const pubsubManager = 'org.moxxmpp.pubsubmanager';
|
||||
const userAvatarManager = 'org.moxxmpp.useravatarmanager';
|
||||
const stableIdManager = 'org.moxxmpp.stableidmanager';
|
||||
const simsManager = 'org.moxxmpp.simsmanager';
|
||||
const messageDeliveryReceiptManager = 'org.moxxmpp.messagedeliveryreceiptmanager';
|
||||
const messageDeliveryReceiptManager =
|
||||
'org.moxxmpp.messagedeliveryreceiptmanager';
|
||||
const chatMarkerManager = 'org.moxxmpp.chatmarkermanager';
|
||||
const oobManager = 'org.moxxmpp.oobmanager';
|
||||
const sfsManager = 'org.moxxmpp.sfsmanager';
|
||||
@@ -19,7 +20,8 @@ const blockingManager = 'org.moxxmpp.blockingmanager';
|
||||
const httpFileUploadManager = 'org.moxxmpp.httpfileuploadmanager';
|
||||
const chatStateManager = 'org.moxxmpp.chatstatemanager';
|
||||
const pingManager = 'org.moxxmpp.ping';
|
||||
const fileUploadNotificationManager = 'org.moxxmpp.fileuploadnotificationmanager';
|
||||
const fileUploadNotificationManager =
|
||||
'org.moxxmpp.fileuploadnotificationmanager';
|
||||
const omemoManager = 'org.moxxmpp.omemomanager';
|
||||
const emeManager = 'org.moxxmpp.ememanager';
|
||||
const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -90,16 +90,21 @@ class MessageManager extends XmppManagerBase {
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(Stanza _, StanzaHandlerData state) async {
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza _,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final message = state.stanza;
|
||||
final body = message.firstTag('body');
|
||||
|
||||
final hints = List<MessageProcessingHint>.empty(growable: true);
|
||||
for (final element in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
|
||||
for (final element
|
||||
in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
|
||||
hints.add(messageProcessingHintFromXml(element));
|
||||
}
|
||||
|
||||
getAttributes().sendEvent(MessageEvent(
|
||||
getAttributes().sendEvent(
|
||||
MessageEvent(
|
||||
body: body != null ? body.innerText() : '',
|
||||
fromJid: JID.fromString(message.attributes['from']! as String),
|
||||
toJid: JID.fromString(message.attributes['to']! as String),
|
||||
@@ -121,13 +126,12 @@ class MessageManager extends XmppManagerBase {
|
||||
messageRetraction: state.messageRetraction,
|
||||
messageCorrectionId: state.lastMessageCorrectionSid,
|
||||
messageReactions: state.messageReactions,
|
||||
messageProcessingHints: hints.isEmpty ?
|
||||
null :
|
||||
hints,
|
||||
messageProcessingHints: hints.isEmpty ? null : hints,
|
||||
stickerPackId: state.stickerPackId,
|
||||
other: state.other,
|
||||
error: StanzaError.fromStanza(message),
|
||||
),);
|
||||
),
|
||||
);
|
||||
|
||||
return state.copyWith(done: true);
|
||||
}
|
||||
@@ -139,7 +143,10 @@ class MessageManager extends XmppManagerBase {
|
||||
/// child in the message stanza and set its id to originId.
|
||||
void sendMessage(MessageDetails details) {
|
||||
assert(
|
||||
implies(details.quoteBody != null, details.quoteFrom != null && details.quoteId != null),
|
||||
implies(
|
||||
details.quoteBody != null,
|
||||
details.quoteFrom != null && details.quoteId != null,
|
||||
),
|
||||
'When quoting a message, then quoteFrom and quoteId must also be non-null',
|
||||
);
|
||||
|
||||
@@ -161,19 +168,14 @@ class MessageManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'reply',
|
||||
xmlns: replyXmlns,
|
||||
attributes: {
|
||||
'to': details.quoteFrom!,
|
||||
'id': details.quoteId!
|
||||
},
|
||||
attributes: {'to': details.quoteFrom!, 'id': details.quoteId!},
|
||||
),
|
||||
)
|
||||
..addChild(
|
||||
XMLNode.xmlns(
|
||||
tag: 'fallback',
|
||||
xmlns: fallbackXmlns,
|
||||
attributes: {
|
||||
'for': replyXmlns
|
||||
},
|
||||
attributes: {'for': replyXmlns},
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'body',
|
||||
@@ -220,7 +222,8 @@ class MessageManager extends XmppManagerBase {
|
||||
stanza.addChild(details.sfs!.toXML());
|
||||
|
||||
final source = details.sfs!.sources.first;
|
||||
if (source is StatelessFileSharingUrlSource && details.setOOBFallbackBody) {
|
||||
if (source is StatelessFileSharingUrlSource &&
|
||||
details.setOOBFallbackBody) {
|
||||
// SFS recommends OOB as a fallback
|
||||
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
|
||||
}
|
||||
@@ -229,7 +232,10 @@ class MessageManager extends XmppManagerBase {
|
||||
if (details.chatState != null) {
|
||||
stanza.addChild(
|
||||
// TODO(Unknown): Move this into xep_0085.dart
|
||||
XMLNode.xmlns(tag: chatStateToString(details.chatState!), xmlns: chatStateXmlns),
|
||||
XMLNode.xmlns(
|
||||
tag: chatStateToString(details.chatState!),
|
||||
xmlns: chatStateXmlns,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@ const vCardTempUpdate = 'vcard-temp:x:update';
|
||||
const pubsubXmlns = 'http://jabber.org/protocol/pubsub';
|
||||
const pubsubEventXmlns = 'http://jabber.org/protocol/pubsub#event';
|
||||
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 pubsubNodeConfigMultiItems = 'http://jabber.org/protocol/pubsub#multi-items';
|
||||
const pubsubNodeConfigMultiItems =
|
||||
'http://jabber.org/protocol/pubsub#multi-items';
|
||||
|
||||
// XEP-0066
|
||||
const oobDataXmlns = 'jabber:x:oob';
|
||||
@@ -42,6 +44,9 @@ const userAvatarMetadataXmlns = 'urn:xmpp:avatar:metadata';
|
||||
// XEP-0085
|
||||
const chatStateXmlns = 'http://jabber.org/protocol/chatstates';
|
||||
|
||||
// XEP-0114
|
||||
const componentAcceptXmlns = 'jabber:component:accept';
|
||||
|
||||
// XEP-0115
|
||||
const capsXmlns = 'http://jabber.org/protocol/caps';
|
||||
|
||||
@@ -114,6 +119,12 @@ const omemoBundlesXmlns = 'urn:xmpp:omemo:2:bundles';
|
||||
// XEP-0385
|
||||
const simsXmlns = 'urn:xmpp:sims:1';
|
||||
|
||||
// XEP-0386
|
||||
const bind2Xmlns = 'urn:xmpp:bind:0';
|
||||
|
||||
// XEP-0388
|
||||
const sasl2Xmlns = 'urn:xmpp:sasl:2';
|
||||
|
||||
// XEP-0420
|
||||
const sceXmlns = 'urn:xmpp:sce:1';
|
||||
|
||||
@@ -137,8 +148,10 @@ const sfsXmlns = 'urn:xmpp:sfs:0';
|
||||
|
||||
// XEP-0448
|
||||
const sfsEncryptionXmlns = 'urn:xmpp:esfs:0';
|
||||
const sfsEncryptionAes128GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-128-gcm-nopadding:0';
|
||||
const sfsEncryptionAes256GcmNoPaddingXmlns = 'urn:xmpp:ciphers:aes-256-gcm-nopadding:0';
|
||||
const sfsEncryptionAes128GcmNoPaddingXmlns =
|
||||
'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';
|
||||
|
||||
// XEP-0449
|
||||
@@ -150,3 +163,6 @@ const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
||||
|
||||
// ???
|
||||
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 streamManagementNegotiator = 'im.moxxmpp.xeps.sm';
|
||||
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:moxxmpp/src/connection.dart';
|
||||
import 'package:moxxmpp/src/errors.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
@@ -27,6 +29,7 @@ abstract class NegotiatorError extends XmppError {}
|
||||
class NegotiatorAttributes {
|
||||
const NegotiatorAttributes(
|
||||
this.sendNonza,
|
||||
this.getConnection,
|
||||
this.getConnectionSettings,
|
||||
this.sendEvent,
|
||||
this.getNegotiatorById,
|
||||
@@ -34,29 +37,59 @@ class NegotiatorAttributes {
|
||||
this.getFullJID,
|
||||
this.getSocket,
|
||||
this.isAuthenticated,
|
||||
this.setAuthenticated,
|
||||
this.setResource,
|
||||
this.removeNegotiatingFeature,
|
||||
);
|
||||
|
||||
/// 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.
|
||||
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;
|
||||
|
||||
/// 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.
|
||||
final T? Function<T extends XmppManagerBase>(String) getManagerById;
|
||||
|
||||
/// Returns the full JID of the current account
|
||||
final JID Function() getFullJID;
|
||||
|
||||
/// Returns the socket the negotiator is attached to
|
||||
final BaseSocketWrapper Function() getSocket;
|
||||
|
||||
/// Returns true if the stream is authenticated. Returns false if not.
|
||||
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 {
|
||||
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
|
||||
/// negotiator be used
|
||||
final int priority;
|
||||
@@ -87,9 +120,13 @@ abstract class XmppFeatureNegotiatorBase {
|
||||
return firstWhereOrNull(
|
||||
features,
|
||||
(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.
|
||||
/// If the negotiator is just elected to be the next one, then [nonza] is equal to
|
||||
/// the <stream:features /> nonza.
|
||||
@@ -106,5 +143,10 @@ abstract class XmppFeatureNegotiatorBase {
|
||||
state = NegotiatorState.ready;
|
||||
}
|
||||
|
||||
@protected
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,62 @@
|
||||
import 'dart:async';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.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 {
|
||||
PingManager() : super(pingManager);
|
||||
PingManager(this._pingDuration) : super(pingManager);
|
||||
|
||||
/// The time between pings, when connected.
|
||||
final Duration _pingDuration;
|
||||
|
||||
/// The actual timer.
|
||||
Timer? _pingTimer;
|
||||
final Lock _timerLock = Lock();
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
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.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onXmppEvent(XmppEvent event) async {
|
||||
if (event is SendPingEvent) {
|
||||
logger.finest('Received ping event.');
|
||||
/// 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();
|
||||
|
||||
@@ -27,11 +67,14 @@ class PingManager extends XmppManagerBase {
|
||||
|
||||
final stream = attrs.getManagerById(smManager) as StreamManagementManager?;
|
||||
if (stream != null) {
|
||||
if (stream.isStreamManagementEnabled() /*&& stream.getUnackedStanzaCount() > 0*/) {
|
||||
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');
|
||||
logger.finest(
|
||||
'Sending a whitespace ping as Stream Management is not enabled',
|
||||
);
|
||||
attrs.getConnection().sendWhitespacePing();
|
||||
} else {
|
||||
_logWarning();
|
||||
@@ -44,5 +87,15 @@ class PingManager extends XmppManagerBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onXmppEvent(XmppEvent event) async {
|
||||
if (event is ConnectionStateChangedEvent) {
|
||||
if (event.connectionEstablished) {
|
||||
await schedulePing();
|
||||
} else {
|
||||
await cancelPing();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/src/connection.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
@@ -6,8 +7,10 @@ import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.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
|
||||
@@ -20,7 +23,8 @@ class PresenceManager extends XmppManagerBase {
|
||||
PresenceManager() : super(presenceManager);
|
||||
|
||||
/// The list of pre-send callbacks.
|
||||
final List<PresencePreSendCallback> _presenceCallbacks = List.empty(growable: true);
|
||||
final List<PresencePreSendCallback> _presenceCallbacks =
|
||||
List.empty(growable: true);
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
@@ -31,7 +35,7 @@ class PresenceManager extends XmppManagerBase {
|
||||
];
|
||||
|
||||
@override
|
||||
List<String> getDiscoFeatures() => [ capsXmlns ];
|
||||
List<String> getDiscoFeatures() => [capsXmlns];
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
@@ -41,23 +45,46 @@ class PresenceManager extends XmppManagerBase {
|
||||
_presenceCallbacks.add(callback);
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
|
||||
@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();
|
||||
switch (presence.type) {
|
||||
case 'subscribe':
|
||||
case 'subscribed': {
|
||||
case 'subscribed':
|
||||
{
|
||||
attrs.sendEvent(
|
||||
SubscriptionRequestReceivedEvent(from: JID.fromString(presence.from!)),
|
||||
SubscriptionRequestReceivedEvent(
|
||||
from: JID.fromString(presence.from!),
|
||||
),
|
||||
);
|
||||
return state.copyWith(done: true);
|
||||
}
|
||||
default: break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (presence.from != null) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -80,11 +107,13 @@ class PresenceManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final attrs = getAttributes();
|
||||
attrs.sendNonza(
|
||||
await attrs.sendStanza(
|
||||
Stanza.presence(
|
||||
from: attrs.getFullJID().toString(),
|
||||
children: children,
|
||||
),
|
||||
awaitable: false,
|
||||
addId: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/util/queue.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// A callback function to be called when the connection to the server has been lost.
|
||||
@@ -17,37 +16,54 @@ abstract class ReconnectionPolicy {
|
||||
/// to perform a reconnection.
|
||||
PerformReconnectFunction? performReconnect;
|
||||
|
||||
/// Function provided by XmppConnection that allows the policy
|
||||
/// to say that we lost the connection.
|
||||
ConnectionLostCallback? triggerConnectionLost;
|
||||
final Lock _lock = Lock();
|
||||
|
||||
/// Indicate if a reconnection attempt is currently running.
|
||||
bool _isReconnecting = false;
|
||||
|
||||
/// Indicate if should try to reconnect.
|
||||
bool _shouldAttemptReconnection = false;
|
||||
|
||||
/// Indicate if a reconnection attempt is currently running.
|
||||
@protected
|
||||
bool isReconnecting = false;
|
||||
Future<bool> canTryReconnecting() async =>
|
||||
_lock.synchronized(() => !_isReconnecting);
|
||||
|
||||
/// And the corresponding lock
|
||||
@protected
|
||||
final Lock lock = Lock();
|
||||
Future<bool> getIsReconnecting() async =>
|
||||
_lock.synchronized(() => _isReconnecting);
|
||||
|
||||
/// The lock for accessing [_shouldAttemptReconnection]
|
||||
@protected
|
||||
final Lock shouldReconnectLock = Lock();
|
||||
Future<void> _resetIsReconnecting() async {
|
||||
await _lock.synchronized(() {
|
||||
_isReconnecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Called by XmppConnection to register the policy.
|
||||
void register(PerformReconnectFunction performReconnect, ConnectionLostCallback triggerConnectionLost) {
|
||||
void register(
|
||||
PerformReconnectFunction performReconnect,
|
||||
) {
|
||||
this.performReconnect = performReconnect;
|
||||
this.triggerConnectionLost = triggerConnectionLost;
|
||||
|
||||
unawaited(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
|
||||
/// 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.
|
||||
Future<void> onFailure() async {}
|
||||
@@ -56,28 +72,13 @@ abstract class ReconnectionPolicy {
|
||||
Future<void> onSuccess();
|
||||
|
||||
Future<bool> getShouldReconnect() async {
|
||||
return shouldReconnectLock.synchronized(() => _shouldAttemptReconnection);
|
||||
return _lock.synchronized(() => _shouldAttemptReconnection);
|
||||
}
|
||||
|
||||
/// Set whether a reconnection attempt should be made.
|
||||
Future<void> setShouldReconnect(bool value) async {
|
||||
return shouldReconnectLock.synchronized(() => _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 lock.synchronized(() => isReconnecting);
|
||||
}
|
||||
|
||||
/// Set the isReconnecting state to [value].
|
||||
@protected
|
||||
Future<void> setIsReconnecting(bool value) async {
|
||||
await lock.synchronized(() async {
|
||||
isReconnecting = value;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// A simple reconnection strategy: Make the reconnection delays exponentially longer
|
||||
@@ -87,7 +88,10 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||
RandomBackoffReconnectionPolicy(
|
||||
this._minBackoffTime,
|
||||
this._maxBackoffTime,
|
||||
) : assert(_minBackoffTime < _maxBackoffTime, '_minBackoffTime must be smaller than _maxBackoffTime'),
|
||||
) : assert(
|
||||
_minBackoffTime < _maxBackoffTime,
|
||||
'_minBackoffTime must be smaller than _maxBackoffTime',
|
||||
),
|
||||
super();
|
||||
|
||||
/// The maximum time in seconds that a backoff should be.
|
||||
@@ -99,83 +103,77 @@ class RandomBackoffReconnectionPolicy extends ReconnectionPolicy {
|
||||
/// Backoff timer.
|
||||
Timer? _timer;
|
||||
|
||||
final Lock _timerLock = Lock();
|
||||
|
||||
/// Logger.
|
||||
final Logger _log = Logger('RandomBackoffReconnectionPolicy');
|
||||
|
||||
/// Event queue
|
||||
final AsyncQueue _eventQueue = AsyncQueue();
|
||||
final Lock _timerLock = Lock();
|
||||
|
||||
/// Called when the backoff expired
|
||||
Future<void> _onTimerElapsed() async {
|
||||
_log.fine('Timer elapsed. Waiting for lock');
|
||||
await lock.synchronized(() async {
|
||||
_log.fine('Lock aquired');
|
||||
if (!(await getShouldReconnect())) {
|
||||
_log.fine('Backoff timer expired but getShouldReconnect() returned false');
|
||||
return;
|
||||
@visibleForTesting
|
||||
Future<void> onTimerElapsed() async {
|
||||
_log.finest('Timer elapsed. Waiting for lock...');
|
||||
final shouldContinue = await _timerLock.synchronized(() async {
|
||||
_log.finest('Timer lock aquired');
|
||||
if (_timer == null) {
|
||||
_log.finest(
|
||||
'The timer is already set to null. Doing nothing.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isReconnecting) {
|
||||
_log.fine('Backoff timer expired but a reconnection is running, so doing nothing.');
|
||||
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');
|
||||
isReconnecting = true;
|
||||
await performReconnect!();
|
||||
});
|
||||
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _reset() async {
|
||||
_log.finest('Resetting internal state');
|
||||
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
});
|
||||
|
||||
await setIsReconnecting(false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reset() async {
|
||||
// ignore: unnecessary_lambdas
|
||||
await _eventQueue.addJob(() => _reset());
|
||||
}
|
||||
|
||||
Future<void> _onFailure() async {
|
||||
final shouldContinue = await _timerLock.synchronized(() {
|
||||
return _timer == null;
|
||||
});
|
||||
if (!shouldContinue) {
|
||||
_log.finest('_onFailure: Not backing off since _timer is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
final seconds = Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
|
||||
_log.finest('Failure occured. Starting random backoff with ${seconds}s');
|
||||
_log.finest('Resetting internal state');
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
|
||||
_timer = Timer(Duration(seconds: seconds), _onTimerElapsed);
|
||||
_timer = null;
|
||||
});
|
||||
await super.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onFailure() async {
|
||||
// ignore: unnecessary_lambdas
|
||||
await _eventQueue.addJob(() => _onFailure());
|
||||
final seconds =
|
||||
Random().nextInt(_maxBackoffTime - _minBackoffTime) + _minBackoffTime;
|
||||
_log.finest('Failure occured. Starting random backoff with ${seconds}s');
|
||||
|
||||
await _timerLock.synchronized(() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(Duration(seconds: seconds), onTimerElapsed);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSuccess() async {
|
||||
await reset();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
bool isTimerRunning() => _timer != null;
|
||||
}
|
||||
|
||||
/// A stub reconnection policy for tests.
|
||||
@@ -190,7 +188,9 @@ class TestingReconnectionPolicy extends ReconnectionPolicy {
|
||||
Future<void> onFailure() async {}
|
||||
|
||||
@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
|
||||
@@ -210,5 +210,7 @@ class TestingSleepReconnectionPolicy extends ReconnectionPolicy {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reset() async {}
|
||||
Future<void> reset() async {
|
||||
await super.reset();
|
||||
}
|
||||
}
|
||||
+26
-11
@@ -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/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: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 {
|
||||
ResourceBindingNegotiator()
|
||||
: super(0, false, bindXmlns, resourceBindingNegotiator);
|
||||
|
||||
ResourceBindingNegotiator() : _requestSent = false, super(0, false, bindXmlns, resourceBindingNegotiator);
|
||||
bool _requestSent;
|
||||
/// Flag indicating the state of the negotiator:
|
||||
/// - True: We sent a binding request
|
||||
/// - False: We have not yet sent the binding request
|
||||
bool _requestSent = false;
|
||||
|
||||
@override
|
||||
bool matchesFeature(List<XMLNode> features) {
|
||||
final sm = attributes.getManagerById<StreamManagementManager>(smManager);
|
||||
if (sm != null) {
|
||||
return super.matchesFeature(features) && !sm.streamResumed && attributes.isAuthenticated();
|
||||
return super.matchesFeature(features) &&
|
||||
!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
|
||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(XMLNode nonza) async {
|
||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||
XMLNode nonza,
|
||||
) async {
|
||||
if (!_requestSent) {
|
||||
final stanza = XMLNode.xmlns(
|
||||
tag: 'iq',
|
||||
@@ -52,10 +68,9 @@ class ResourceBindingNegotiator extends XmppFeatureNegotiatorBase {
|
||||
}
|
||||
|
||||
final bind = nonza.firstTag('bind')!;
|
||||
final jid = bind.firstTag('jid')!;
|
||||
final resource = jid.innerText().split('/')[1];
|
||||
|
||||
await attributes.sendEvent(ResourceBindingSuccessEvent(resource: resource));
|
||||
final rawJid = bind.firstTag('jid')!.innerText();
|
||||
final resource = JID.fromString(rawJid).resource;
|
||||
attributes.setResource(resource);
|
||||
return const Result(NegotiatorState.done);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
abstract class SaslError extends NegotiatorError {
|
||||
static SaslError fromFailure(XMLNode failure) {
|
||||
XMLNode? error;
|
||||
for (final child in failure.children) {
|
||||
if (child.tag == 'text') continue;
|
||||
|
||||
error = child;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (error?.tag) {
|
||||
case 'credentials-expired':
|
||||
return SaslCredentialsExpiredError();
|
||||
case 'not-authorized':
|
||||
return SaslNotAuthorizedError();
|
||||
case 'account-disabled':
|
||||
return SaslAccountDisabledError();
|
||||
}
|
||||
|
||||
return SaslUnspecifiedError();
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <not-authorized /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-not-authorized).
|
||||
class SaslNotAuthorizedError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <credentials-expired /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-credentials-expired).
|
||||
class SaslCredentialsExpiredError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// Triggered when the server returned us a <account-disabled /> failure during SASL
|
||||
/// (https://xmpp.org/rfcs/rfc6120.html#sasl-errors-account-disabled).
|
||||
class SaslAccountDisabledError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
/// An unspecified SASL error, i.e. everything not matched by any more precise erorr
|
||||
/// class.
|
||||
class SaslUnspecifiedError extends SaslError {
|
||||
@override
|
||||
bool isRecoverable() => true;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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;
|
||||
}
|
||||
+4
-2
@@ -4,8 +4,9 @@ import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
abstract class SaslNegotiator extends XmppFeatureNegotiatorBase {
|
||||
SaslNegotiator(int priority, String id, this.mechanismName)
|
||||
: super(priority, true, saslXmlns, id);
|
||||
|
||||
SaslNegotiator(int priority, String id, this.mechanismName) : super(priority, true, saslXmlns, id);
|
||||
/// The name inside the <mechanism /> element
|
||||
final String mechanismName;
|
||||
|
||||
@@ -22,6 +23,7 @@ abstract class SaslNegotiator extends XmppFeatureNegotiatorBase {
|
||||
return firstWhereOrNull(
|
||||
mechanisms.children,
|
||||
(XMLNode mechanism) => mechanism.text == mechanismName,
|
||||
) != null;
|
||||
) !=
|
||||
null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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/rfcs/rfc_6120/sasl/errors.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||
import 'package:saslprep/saslprep.dart';
|
||||
|
||||
class SaslPlainAuthNonza extends SaslAuthNonza {
|
||||
SaslPlainAuthNonza(String data)
|
||||
: super(
|
||||
'PLAIN',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
SaslPlainNegotiator()
|
||||
: _authSent = false,
|
||||
_log = Logger('SaslPlainNegotiator'),
|
||||
super(0, saslPlainNegotiator, 'PLAIN');
|
||||
bool _authSent;
|
||||
|
||||
final Logger _log;
|
||||
|
||||
@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 {
|
||||
if (!_authSent) {
|
||||
final data = await getRawStep('');
|
||||
attributes.sendNonza(
|
||||
SaslPlainAuthNonza(data),
|
||||
);
|
||||
_authSent = true;
|
||||
return const Result(NegotiatorState.ready);
|
||||
} else {
|
||||
final tag = nonza.tag;
|
||||
if (tag == 'success') {
|
||||
attributes.setAuthenticated();
|
||||
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(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_authSent = false;
|
||||
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> postRegisterCallback() async {
|
||||
attributes
|
||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
||||
?.registerSaslNegotiator(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getRawStep(String input) async {
|
||||
final settings = attributes.getConnectionSettings();
|
||||
final prep = Saslprep.saslprep(settings.password);
|
||||
return base64.encode(
|
||||
utf8.encode('\u0000${settings.jid.local}\u0000$prep'),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
state = NegotiatorState.done;
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||
|
||||
@override
|
||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
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/rfcs/rfc_6120/sasl/errors.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/kv.dart';
|
||||
import 'package:moxxmpp/src/rfcs/rfc_6120/sasl/nonza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/types/result.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0388/negotiators.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0388/xep_0388.dart';
|
||||
import 'package:random_string/random_string.dart';
|
||||
import 'package:saslprep/saslprep.dart';
|
||||
|
||||
abstract class SaslScramError extends NegotiatorError {}
|
||||
|
||||
class NoAdditionalDataError extends SaslScramError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
class InvalidServerSignatureError extends SaslScramError {
|
||||
@override
|
||||
bool isRecoverable() => false;
|
||||
}
|
||||
|
||||
// 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 Sasl2AuthenticationNegotiator {
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool _checkSignature(String base64Signature) {
|
||||
final signature =
|
||||
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
||||
return signature['v']! == _serverSignature;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||
XMLNode nonza,
|
||||
) async {
|
||||
switch (_scramState) {
|
||||
case ScramState.preSent:
|
||||
attributes.sendNonza(
|
||||
SaslScramAuthNonza(
|
||||
body: await getRawStep(''),
|
||||
type: hashType,
|
||||
),
|
||||
);
|
||||
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(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
attributes.sendNonza(
|
||||
SaslScramResponseNonza(body: await getRawStep(nonza.innerText())),
|
||||
);
|
||||
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(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
if (!_checkSignature(nonza.innerText())) {
|
||||
// TODO(Unknown): Notify of a signature mismatch
|
||||
//final error = nonza.children.first.tag;
|
||||
//attributes.sendEvent(AuthenticationFailedEvent(error));
|
||||
_scramState = ScramState.error;
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
|
||||
attributes.setAuthenticated();
|
||||
return const Result(NegotiatorState.done);
|
||||
case ScramState.error:
|
||||
return Result(
|
||||
SaslError.fromFailure(nonza),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void reset() {
|
||||
_scramState = ScramState.preSent;
|
||||
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getRawStep(String input) 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;
|
||||
return base64.encode(utf8.encode(gs2Header + initialMessageNoGS2));
|
||||
case ScramState.initialMessageSent:
|
||||
final challengeBase64 = input;
|
||||
final response = await calculateChallengeResponse(challengeBase64);
|
||||
final responseBase64 = base64.encode(utf8.encode(response));
|
||||
_scramState = ScramState.challengeResponseSent;
|
||||
|
||||
return responseBase64;
|
||||
case ScramState.challengeResponseSent:
|
||||
case ScramState.error:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> postRegisterCallback() async {
|
||||
attributes
|
||||
.getNegotiatorById<Sasl2Negotiator>(sasl2Negotiator)
|
||||
?.registerSaslNegotiator(this);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
|
||||
return [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onSasl2Failure(XMLNode response) async {}
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
// When we're done with SASL2, check the additional data to verify the server
|
||||
// signature.
|
||||
state = NegotiatorState.done;
|
||||
final additionalData = response.firstTag('additional-data');
|
||||
if (additionalData == null) {
|
||||
return Result(NoAdditionalDataError());
|
||||
}
|
||||
|
||||
if (!_checkSignature(additionalData.innerText())) {
|
||||
return Result(InvalidServerSignatureError());
|
||||
}
|
||||
|
||||
return const Result(true);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 200 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user