Compare commits
63 Commits
94d6fe4925
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c61ddeb338 | |||
| e2515e25e4 | |||
| 09a849c6eb | |||
| 9eb94e5f48 | |||
| db77790bf4 | |||
| 7ceee48d31 | |||
| 941c3e4fd8 | |||
| 365ff2f238 | |||
| b3c8a6cd2f | |||
| d4166d087e | |||
| ddf781daff | |||
| 5973076b89 | |||
| 72cb76d1f6 | |||
| be7581e841 | |||
| 8a2435e4ad | |||
| 97f082b6f5 | |||
| f287d501ab | |||
| 93e9d6ca22 | |||
| 007cdce53d | |||
| 6d3a5e98de | |||
| e97d6e6517 | |||
| 882d20dc7a | |||
| 1f712151e4 | |||
| e7922668b1 | |||
| 87866bf3f5 | |||
| 41b789fa28 | |||
| 0a68f09fb4 | |||
| edf1d0b257 | |||
| 59b90307c2 | |||
| 49d3c6411b | |||
| 3a94dd9634 | |||
| fb4b4c71e2 | |||
| d9fbb9e102 | |||
| aba90f2e90 | |||
| 9211963390 | |||
| c7d58c3d3f | |||
| 6dbbf08be4 | |||
| 7ca648c478 | |||
| 814f99436b | |||
| 5bd2466c54 | |||
| 14b62cef96 | |||
| c3088f9046 | |||
| 64b93b536e | |||
| c1c48d0a83 | |||
| 4a681b9483 | |||
| c504afc944 | |||
| 76a9f7be7a | |||
| afa3927720 | |||
| 5f36289f50 | |||
| fbe3b90200 | |||
| d7c13abde6 | |||
| d4416c8a47 | |||
| 9666557655 | |||
| 1625f912b0 | |||
| 864cc0e747 | |||
| c9e817054d | |||
| d57bf2ef80 | |||
| 8bfdd5e54a | |||
| e58082bf38 | |||
| dbb945b424 | |||
| 2431eafa6c | |||
| 264ab130ee | |||
| 38dba0e6b7 |
2
.gitlint
2
.gitlint
@@ -7,7 +7,7 @@ line-length=72
|
||||
[title-trailing-punctuation]
|
||||
[title-hard-tab]
|
||||
[title-match-regex]
|
||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all|flake)+(,(meta|tests|style|docs|xep|core|example|all|flake))*\)|release): [A-Z0-9].*$
|
||||
regex=^((feat|fix|chore|refactor|docs|release|test)\((meta|tests|style|docs|xep|core|example|all|flake|ci)+(,(meta|tests|style|docs|xep|core|example|all|flake|ci))*\)|release): [A-Z0-9].*$
|
||||
|
||||
|
||||
[body-trailing-whitespace]
|
||||
|
||||
@@ -1,28 +1,49 @@
|
||||
when:
|
||||
branch: master
|
||||
|
||||
pipeline:
|
||||
# Check moxxmpp
|
||||
moxxmpp-lint:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- cd packages/moxxmpp
|
||||
- dart pub get
|
||||
# Proxy requests to pub.dev using pubcached
|
||||
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
- dart analyze --fatal-infos --fatal-warnings
|
||||
|
||||
moxxmpp-test:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- cd packages/moxxmpp
|
||||
- dart pub get
|
||||
# Proxy requests to pub.dev using pubcached
|
||||
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
- dart test
|
||||
|
||||
# Check moxxmpp_socket_tcp
|
||||
moxxmpp_socket_tcp-lint:
|
||||
image: dart:2.18.1
|
||||
image: dart:3.0.7
|
||||
commands:
|
||||
- cd packages/moxxmpp_socket_tcp
|
||||
- dart pub get
|
||||
# Proxy requests to pub.dev using pubcached
|
||||
- PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
- dart analyze --fatal-infos --fatal-warnings
|
||||
|
||||
# moxxmpp-test:
|
||||
# image: dart:2.18.1
|
||||
# image: dart:3.0.7
|
||||
# commands:
|
||||
# - cd packages/moxxmpp
|
||||
# - dart pub get
|
||||
# # Proxy requests to pub.dev using pubcached
|
||||
# - PUB_HOSTED_URL=http://172.17.0.1:8000 dart pub get
|
||||
# - dart test
|
||||
|
||||
notify:
|
||||
image: git.polynom.me/papatutuwawa/woodpecker-xmpp
|
||||
settings:
|
||||
xmpp_tls: 1
|
||||
xmpp_is_muc: 1
|
||||
xmpp_recipient: moxxy-build@muc.moxxy.org
|
||||
xmpp_alias: 2Bot
|
||||
secrets: [ xmpp_jid, xmpp_password, xmpp_server ]
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
|
||||
@@ -7,7 +7,7 @@ moxxmpp is a XMPP library written purely in Dart for usage in Moxxy.
|
||||
|
||||
This package contains the actual XMPP code that is platform-independent.
|
||||
|
||||
Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||
Documentation is available [here](https://docs.moxxy.org/moxxmpp/index.html).
|
||||
|
||||
### [moxxmpp_socket_tcp](./packages/moxxmpp_socket_tcp)
|
||||
|
||||
@@ -15,6 +15,10 @@ Documentation is available [here](https://moxxy.org/developers/docs/moxxmpp/).
|
||||
implements the RFC6120 connection algorithm and XEP-0368 direct TLS connections,
|
||||
if a DNS implementation is given, and supports StartTLS.
|
||||
|
||||
### moxxmpp_color
|
||||
|
||||
Implementation of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
|
||||
|
||||
## Development
|
||||
|
||||
To begin, use [melos](https://github.com/invertase/melos) to bootstrap the project: `melos bootstrap`. Then, the example
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
|
||||
@@ -38,6 +38,7 @@ void main(List<String> args) async {
|
||||
DiscoManager([]),
|
||||
PubSubManager(),
|
||||
MessageManager(),
|
||||
StableIdManager(),
|
||||
MUCManager(),
|
||||
]);
|
||||
await connection.registerFeatureNegotiators([
|
||||
@@ -57,8 +58,33 @@ void main(List<String> args) async {
|
||||
}
|
||||
Logger.root.info('Connected.');
|
||||
|
||||
// Print received messages.
|
||||
connection
|
||||
.asBroadcastStream()
|
||||
.where((event) => event is MessageEvent)
|
||||
.listen((event) {
|
||||
event as MessageEvent;
|
||||
|
||||
// Ignore messages with no <body />
|
||||
final body = event.get<MessageBodyData>()?.body;
|
||||
if (body == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
print('=====> [${event.from}] $body');
|
||||
});
|
||||
|
||||
// Join room
|
||||
await connection.getManagerById<MUCManager>(mucManager)!.joinRoom(muc, nick);
|
||||
final mm = connection.getManagerById<MUCManager>(mucManager)!;
|
||||
await mm.joinRoom(
|
||||
muc,
|
||||
nick,
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
final state = (await mm.getRoomState(muc))!;
|
||||
|
||||
print('=====> ${state.members.length} users in room');
|
||||
print('=====> ${state.members.values.map((m) => m.nick).join(", ")}');
|
||||
|
||||
final repl = Repl(prompt: '> ');
|
||||
await for (final line in repl.runAsync()) {
|
||||
@@ -68,6 +94,11 @@ void main(List<String> args) async {
|
||||
muc,
|
||||
TypedMap<StanzaHandlerExtension>.fromList([
|
||||
MessageBodyData(line),
|
||||
StableIdData(
|
||||
// NOTE: Don't do this. Use a UUID.
|
||||
DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
null,
|
||||
),
|
||||
]),
|
||||
type: 'groupchat');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ void main(List<String> args) async {
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
ExampleTCPSocketWrapper(parser.srvRecord),
|
||||
ExampleTCPSocketWrapper(parser.srvRecord, true),
|
||||
)..connectionSettings = parser.connectionSettings;
|
||||
|
||||
// Generate OMEMO data
|
||||
|
||||
112
examples_dart/bin/simple_client.dart
Normal file
112
examples_dart/bin/simple_client.dart
Normal file
@@ -0,0 +1,112 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
|
||||
/// The JID we want to authenticate as.
|
||||
final xmppUser = JID.fromString('jane@example.com');
|
||||
|
||||
/// The password to authenticate with.
|
||||
const xmppPass = 'secret';
|
||||
|
||||
/// The [xmppHost]:[xmppPort] server address to connect to.
|
||||
/// In a real application, one might prefer to use [TCPSocketWrapper]
|
||||
/// with a custom DNS implementation to let moxxmpp resolve the XMPP
|
||||
/// server's address automatically. However, if we just provide a host
|
||||
/// and a port, then [TCPSocketWrapper] will just skip the resolution and
|
||||
/// immediately use the provided connection details.
|
||||
const xmppHost = 'localhost';
|
||||
const xmppPort = 5222;
|
||||
|
||||
void main(List<String> args) async {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
print('${record.level.name}|${record.time}: ${record.message}');
|
||||
});
|
||||
|
||||
// This class manages every aspect of handling the XMPP stream.
|
||||
final connection = XmppConnection(
|
||||
// A reconnection policy tells the connection how to handle an error
|
||||
// while or after connecting to the server. The [TestingReconnectionPolicy]
|
||||
// immediately triggers a reconnection. In a real implementation, one might
|
||||
// prefer to use a smarter strategy, like using an exponential backoff.
|
||||
TestingReconnectionPolicy(),
|
||||
|
||||
// A connectivity manager tells the connection when it can connect. This is to
|
||||
// ensure that we're not constantly trying to reconnect because we have no
|
||||
// Internet connection. [AlwaysConnectedConnectivityManager] always says that
|
||||
// we're connected. In a real application, one might prefer to use a smarter
|
||||
// strategy, like using connectivity_plus to query the system's network connectivity
|
||||
// state.
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
|
||||
// This kind of negotiator tells the connection how to handle the stream
|
||||
// negotiations. The [ClientToServerNegotiator] allows to connect to the server
|
||||
// as a regular client. Another negotiator would be the [ComponentToServerNegotiator] that
|
||||
// allows for connections to the server where we're acting as a component.
|
||||
ClientToServerNegotiator(),
|
||||
|
||||
// A wrapper around any kind of connection. In this case, we use the [TCPSocketWrapper], which
|
||||
// uses a dart:io Socket/SecureSocket to connect to the server. If you want, you can also
|
||||
// provide your own socket to use, for example, WebSockets or any other connection
|
||||
// mechanism.
|
||||
TCPSocketWrapper(false),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: xmppUser,
|
||||
password: xmppPass,
|
||||
host: xmppHost,
|
||||
port: xmppPort,
|
||||
);
|
||||
|
||||
// Register a set of "managers" that provide you with implementations of various
|
||||
// XEPs. Some have interdependencies, which need to be met. However, this example keeps
|
||||
// it simple and just registers a [MessageManager], which has no required dependencies.
|
||||
await connection.registerManagers([
|
||||
// The [MessageManager] handles receiving and sending <message /> stanzas.
|
||||
MessageManager(),
|
||||
]);
|
||||
|
||||
// Feature negotiators are objects that tell the connection negotiator what stream features
|
||||
// we can negotiate and enable. moxxmpp negotiators always try to enable their features.
|
||||
await connection.registerFeatureNegotiators([
|
||||
// This negotiator authenticates to the server using SASL PLAIN with the provided
|
||||
// credentials.
|
||||
SaslPlainNegotiator(),
|
||||
// This negotiator attempts to bind a resource. By default, it's always a random one.
|
||||
ResourceBindingNegotiator(),
|
||||
// This negotiator attempts to do StartTLS before authenticating.
|
||||
StartTlsNegotiator(),
|
||||
]);
|
||||
|
||||
// Set up a stream handler for the connection's event stream. Managers and negotiators
|
||||
// may trigger certain events. The [MessageManager], for example, triggers a [MessageEvent]
|
||||
// whenever a message is received. If other managers are registered that parse a message's
|
||||
// contents, then they can add their data to the event.
|
||||
connection.asBroadcastStream().listen((event) {
|
||||
if (event is! MessageEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The text body (contents of the <body /> element) are returned as a
|
||||
// [MessageBodyData] object. However, a message does not have to contain a
|
||||
// body, so it is nullable.
|
||||
final body = event.extensions.get<MessageBodyData>()?.body;
|
||||
print('[<-- ${event.from}] $body');
|
||||
});
|
||||
|
||||
// Connect to the server.
|
||||
final result = await connection.connect(
|
||||
// This flag indicates that we want to reconnect in case something happens.
|
||||
shouldReconnect: true,
|
||||
// This flag indicates that we want the returned Future to only resolve
|
||||
// once the stream negotiations are done and no negotiator has any feature left
|
||||
// to negotiate.
|
||||
waitUntilLogin: true,
|
||||
);
|
||||
|
||||
// Check if the connection was successful. [connection.connect] can return a boolean
|
||||
// to indicate success or a [XmppError] in case the connection attempt failed.
|
||||
if (!result.isType<bool>()) {
|
||||
print('Failed to connect to server');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
/// A simple socket for examples that allows injection of SRV records (since
|
||||
/// we cannot use moxdns here).
|
||||
class ExampleTCPSocketWrapper extends TCPSocketWrapper {
|
||||
ExampleTCPSocketWrapper(this.srvRecord);
|
||||
ExampleTCPSocketWrapper(this.srvRecord, bool logData) : super(logData);
|
||||
|
||||
/// A potential SRV record to inject for testing.
|
||||
final MoxSrvRecord? srvRecord;
|
||||
|
||||
@@ -3,7 +3,7 @@ description: A collection of samples for moxxmpp.
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
args: 2.4.1
|
||||
@@ -12,10 +12,10 @@ dependencies:
|
||||
logging: ^1.0.2
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.3.1
|
||||
version: 0.4.0
|
||||
moxxmpp_socket_tcp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.3.1
|
||||
version: 0.4.0
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: ^0.5.1
|
||||
|
||||
44
flake.lock
generated
44
flake.lock
generated
@@ -7,11 +7,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689798050,
|
||||
"narHash": "sha256-ZyFPra7N0MF803o55dYQQyX9b/BmXr6QTCyN7slRThY=",
|
||||
"lastModified": 1727554699,
|
||||
"narHash": "sha256-puBCNL5PW7Pej+6Srmi2YjEgNeE015NFe33hbkkLqeQ=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "9aa0e2990da86de8ca203af313668851dcb9ea6e",
|
||||
"rev": "bc34ef1c71fe9eafcfb1d637b431fca83d746625",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -25,15 +25,14 @@
|
||||
"nixpkgs": [
|
||||
"android-nixpkgs",
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688380630,
|
||||
"narHash": "sha256-8ilApWVb1mAi4439zS3iFeIT0ODlbrifm/fegWwgHjA=",
|
||||
"lastModified": 1722113426,
|
||||
"narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "f9238ec3d75cefbb2b42a44948c4e8fb1ae9a205",
|
||||
"rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -44,14 +43,14 @@
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -61,12 +60,15 @@
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -77,11 +79,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1689679375,
|
||||
"narHash": "sha256-LHUC52WvyVDi9PwyL1QCpaxYWBqp4ir4iL6zgOkmcb8=",
|
||||
"lastModified": 1727348695,
|
||||
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "684c17c429c42515bafb3ad775d2a710947f3d67",
|
||||
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -93,11 +95,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1689752456,
|
||||
"narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=",
|
||||
"lastModified": 1727586919,
|
||||
"narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7f256d7da238cb627ef189d56ed590739f42f13b",
|
||||
"rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
51
flake.nix
51
flake.nix
@@ -15,20 +15,22 @@
|
||||
};
|
||||
};
|
||||
# Everything to make Flutter happy
|
||||
sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [
|
||||
cmdline-tools-latest
|
||||
build-tools-30-0-3
|
||||
build-tools-33-0-2
|
||||
build-tools-34-0-0
|
||||
platform-tools
|
||||
emulator
|
||||
patcher-v4
|
||||
platforms-android-28
|
||||
platforms-android-29
|
||||
platforms-android-30
|
||||
platforms-android-31
|
||||
platforms-android-33
|
||||
]);
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
# TODO: Find a way to pin these
|
||||
#toolsVersion = "26.1.1";
|
||||
#platformToolsVersion = "31.0.3";
|
||||
#buildToolsVersions = [ "31.0.0" ];
|
||||
#includeEmulator = true;
|
||||
#emulatorVersion = "30.6.3";
|
||||
platformVersions = [ "28" ];
|
||||
includeSources = false;
|
||||
includeSystemImages = true;
|
||||
systemImageTypes = [ "default" ];
|
||||
abiVersions = [ "x86_64" ];
|
||||
includeNDK = false;
|
||||
useGoogleAPIs = false;
|
||||
useGoogleTVAddOns = false;
|
||||
};
|
||||
lib = pkgs.lib;
|
||||
pinnedJDK = pkgs.jdk17;
|
||||
|
||||
@@ -68,7 +70,7 @@
|
||||
};
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
flutter37 pinnedJDK sdk dart # Dart
|
||||
flutter pinnedJDK android.platform-tools dart # Dart
|
||||
gitlint # Code hygiene
|
||||
ripgrep # General utilities
|
||||
|
||||
@@ -100,13 +102,26 @@
|
||||
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
|
||||
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
|
||||
|
||||
ANDROID_SDK_ROOT = "${sdk}/share/android-sdk";
|
||||
ANDROID_HOME = "${sdk}/share/android-sdk";
|
||||
ANDROID_SDK_ROOT = "${android.androidsdk}/share/android-sdk";
|
||||
ANDROID_HOME = "${android.androidsdk}/share/android-sdk";
|
||||
JAVA_HOME = pinnedJDK;
|
||||
|
||||
# Fix an issue with Flutter using an older version of aapt2, which does not know
|
||||
# an used parameter.
|
||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2";
|
||||
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${android.androidsdk}/share/android-sdk/build-tools/34.0.0/aapt2";
|
||||
};
|
||||
|
||||
apps = {
|
||||
regenerateNixPackage = let
|
||||
script = pkgs.writeShellScript "regenerate-nix-package.sh" ''
|
||||
set -e
|
||||
${pythonEnv}/bin/python ./scripts/pubspec2lock.py ./packages/moxxmpp/pubspec.lock ./nix/moxxmpp.lock
|
||||
${pythonEnv}/bin/python ./scripts/lock2nix.py ./nix/moxxmpp.lock ./nix/pubcache.moxxmpp.nix moxxmpp
|
||||
'';
|
||||
in {
|
||||
type = "app";
|
||||
program = "${script}";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
set -ex
|
||||
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
|
||||
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123
|
||||
@@ -3,14 +3,16 @@ description: A sample command-line application.
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
logging: ^1.0.2
|
||||
moxxmpp: 0.3.0
|
||||
moxxmpp_socket_tcp: 0.3.0
|
||||
logging: ^1.3.0
|
||||
moxxmpp:
|
||||
path: ../packages/moxxmpp
|
||||
moxxmpp_socket_tcp:
|
||||
path: ../packages/moxxmpp_socket_tcp
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
very_good_analysis: ^3.0.1
|
||||
build_runner: ^2.4.13
|
||||
test: ^1.25.8
|
||||
very_good_analysis: ^6.0.0
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class TestingTCPSocketWrapper extends TCPSocketWrapper {
|
||||
TestingTCPSocketWrapper() : super(true);
|
||||
|
||||
@override
|
||||
bool onBadCertificate(dynamic certificate, String domain) {
|
||||
return true;
|
||||
@@ -27,7 +29,7 @@ void main() {
|
||||
ClientToServerNegotiator(),
|
||||
TestingTCPSocketWrapper(),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('testuser@localhost'),
|
||||
jid: JID.fromString('testuser1@localhost'),
|
||||
password: 'abc123',
|
||||
host: '127.0.0.1',
|
||||
port: 5222,
|
||||
@@ -40,6 +42,8 @@ void main() {
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(9, '', '', ScramHashType.sha1),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha256),
|
||||
ResourceBindingNegotiator(),
|
||||
FASTSaslNegotiator(),
|
||||
Bind2Negotiator(),
|
||||
|
||||
609
nix/moxxmpp.lock
609
nix/moxxmpp.lock
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,30 @@
|
||||
# GENERATED BY LOCK2NIX.py
|
||||
# DO NOT EDIT BY HAND
|
||||
{fetchzip, runCommand} : rec {
|
||||
_fe_analyzer_shared = fetchzip {
|
||||
sha256 = "1hyd5pmjcfyvfwhsc0wq6k0229abmqq5zn95g31hh42bklb2gci5";
|
||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/50.0.0.tar.gz";
|
||||
sha256 = "15fh9ka41dw4qsynv07msq4i243fibprcmafdygw5x88f7m55fq3";
|
||||
url = "https://pub.dartlang.org/packages/_fe_analyzer_shared/versions/61.0.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
analyzer = fetchzip {
|
||||
sha256 = "0niy5b3w39aywpjpw5a84pxdilhh3zzv1c22x8ywml756pybmj4r";
|
||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.2.0.tar.gz";
|
||||
sha256 = "0w604zngxwfx0xqxvhbxrhdh04wgm6ad6a1lbwnyvmk57amv44np";
|
||||
url = "https://pub.dartlang.org/packages/analyzer/versions/5.13.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
args = fetchzip {
|
||||
sha256 = "0c78zkzg2d2kzw1qrpiyrj1qvm4pr0yhnzapbqk347m780ha408g";
|
||||
url = "https://pub.dartlang.org/packages/args/versions/2.3.1.tar.gz";
|
||||
sha256 = "01ps253280c6dbx0vncw4wga4l2qp1zx779qjj2x06xzb3744zbz";
|
||||
url = "https://pub.dartlang.org/packages/args/versions/2.4.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
async = fetchzip {
|
||||
sha256 = "00hhylamsjcqmcbxlsrfimri63gb384l31r9mqvacn6c6bvk4yfx";
|
||||
url = "https://pub.dartlang.org/packages/async/versions/2.10.0.tar.gz";
|
||||
sha256 = "0hfgvjajp5c2mw68186hgrk9v5zjhhi149hlhl0fap274p2v1g3q";
|
||||
url = "https://pub.dartlang.org/packages/async/versions/2.11.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -49,29 +51,29 @@
|
||||
};
|
||||
|
||||
build_daemon = fetchzip {
|
||||
sha256 = "0b6hnwjc3gi5g7cnpy8xyiqigcrs0xp51c7y7v1pqn9v75g25w6j";
|
||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.0.tar.gz";
|
||||
sha256 = "1wn7bq846vgdj62bkh9h25l95xdsndv0jdyw52nyr0591l3bpg3h";
|
||||
url = "https://pub.dartlang.org/packages/build_daemon/versions/3.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_resolvers = fetchzip {
|
||||
sha256 = "0fnrisgq6rnvbqsf8v43hb11kr1qq6azrxbsvx3wwimd37nxx8m5";
|
||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.1.0.tar.gz";
|
||||
sha256 = "00h9abhrfmnl0xxziyf6p68sxnbv2ww1c4dhgpnz00mzbmamnq5c";
|
||||
url = "https://pub.dartlang.org/packages/build_resolvers/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner = fetchzip {
|
||||
sha256 = "0246bxl9rxgil55fhfzi7csd9a56blj9s1j1z79717hiyzsr60x6";
|
||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.2.tar.gz";
|
||||
sha256 = "0b5ha1l6k0gn2swqgqvfy2vl58klf81sxrjnmk0p7rj1wzbqjm7l";
|
||||
url = "https://pub.dartlang.org/packages/build_runner/versions/2.3.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
build_runner_core = fetchzip {
|
||||
sha256 = "0bpil0fw0dag3vbnin9p945ymi7xjgkiy7jrq9j52plljf7cnf5z";
|
||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7.tar.gz";
|
||||
sha256 = "07r1kfy6ylm4i4xrb24ns8l26h4h1lgcskmnf8wvq2rd5d5hq790";
|
||||
url = "https://pub.dartlang.org/packages/build_runner_core/versions/7.2.7%2B1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -84,29 +86,29 @@
|
||||
};
|
||||
|
||||
built_value = fetchzip {
|
||||
sha256 = "0sslr4258snvcj8qhbdk6wapka174als0viyxddwqlnhs7dlci8i";
|
||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.4.2.tar.gz";
|
||||
sha256 = "1y84imf9xqqy3gnd5zz9bcln6mycy7qx35r70b0izm31ismlbzkv";
|
||||
url = "https://pub.dartlang.org/packages/built_value/versions/8.6.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
checked_yaml = fetchzip {
|
||||
sha256 = "1gf7ankc5jb7mk17br87ajv05pfg6vb8nf35ay6c35w8jp70ra7k";
|
||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.1.tar.gz";
|
||||
sha256 = "1sn01yrmj0pkijn08g3v45c3zmyvdygk9svigkkzybgicdwlkpqs";
|
||||
url = "https://pub.dartlang.org/packages/checked_yaml/versions/2.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
code_builder = fetchzip {
|
||||
sha256 = "1vl9dl23yd0zjw52ndrazijs6dw83fg1rvyb2gfdpd6n1lj9nbhg";
|
||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.3.0.tar.gz";
|
||||
sha256 = "1shgl7mxiyv0hhw326yqj2b9jxi1h74qxmsnxf1d1xc6yz766p9a";
|
||||
url = "https://pub.dartlang.org/packages/code_builder/versions/4.6.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
collection = fetchzip {
|
||||
sha256 = "1iyl3v3j7mj3sxjf63b1kc182fwrwd04mjp5x2i61hic8ihfw545";
|
||||
url = "https://pub.dartlang.org/packages/collection/versions/1.17.0.tar.gz";
|
||||
sha256 = "1mr8j0078c4z9hhckiq8m735rggsazwfprm0w9gisil51vh7j2mk";
|
||||
url = "https://pub.dartlang.org/packages/collection/versions/1.18.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -119,29 +121,29 @@
|
||||
};
|
||||
|
||||
coverage = fetchzip {
|
||||
sha256 = "0akbg1yp2h4vprc8r9xvrpgvp5d26h7m80h5sbzgr5dlis1bcw0d";
|
||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.1.tar.gz";
|
||||
sha256 = "1yy9bgkax5b6kk7qa07p452v82fyj4rl1j03fn366ywyvhfrh6lp";
|
||||
url = "https://pub.dartlang.org/packages/coverage/versions/1.6.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
crypto = fetchzip {
|
||||
sha256 = "1kjfb8fvdxazmv9ps2iqdhb8kcr31115h0nwn6v4xmr71k8jb8ds";
|
||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.2.tar.gz";
|
||||
sha256 = "100ai8qa4p3dyvvd60c4xa9p0gm06yh0d68xgcfm3giraad8xmqj";
|
||||
url = "https://pub.dartlang.org/packages/crypto/versions/3.0.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
cryptography = fetchzip {
|
||||
sha256 = "0jqph45d9lbhdakprnb84c3qhk4aq05hhb1pmn8w23yhl41ypijs";
|
||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.0.5.tar.gz";
|
||||
sha256 = "1yxn9slqq93ri81fbr2nbsinz0mpk9wk39ny076ja8q31d4i8v3f";
|
||||
url = "https://pub.dartlang.org/packages/cryptography/versions/2.5.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
dart_style = fetchzip {
|
||||
sha256 = "01wg15kalbjlh4i3xbawc9zk8yrk28qhak7xp7mlwn2syhdckn7v";
|
||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.2.4.tar.gz";
|
||||
sha256 = "0cjhrb1hs8iw9smmfd0fgnxq3nm0w8sz17l6q6svyz6kif19wk9k";
|
||||
url = "https://pub.dartlang.org/packages/dart_style/versions/2.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -154,43 +156,29 @@
|
||||
};
|
||||
|
||||
fixnum = fetchzip {
|
||||
sha256 = "1m8cdfqp9d6w1cik3fwz9bai1wf9j11rjv2z0zlv7ich87q9kkjk";
|
||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
freezed = fetchzip {
|
||||
sha256 = "1i9s4djf4vlz56zqn8brcck3n7sk07qay23wmaan991cqydd10iq";
|
||||
url = "https://pub.dartlang.org/packages/freezed/versions/2.1.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
freezed_annotation = fetchzip {
|
||||
sha256 = "0ym120dh1lpfnb68gxh1finm8p9l445q5x10aw8269y469b9k9z3";
|
||||
url = "https://pub.dartlang.org/packages/freezed_annotation/versions/2.1.0.tar.gz";
|
||||
sha256 = "0nqrzj41ys8dpxf1x70r0kfj1avj0f2j2b7498k8kvc0i9c5asz7";
|
||||
url = "https://pub.dartlang.org/packages/fixnum/versions/1.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
frontend_server_client = fetchzip {
|
||||
sha256 = "0nv4avkv2if9hdcfzckz36f3mclv7vxchivrg8j3miaqhnjvv4bj";
|
||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.1.0.tar.gz";
|
||||
sha256 = "096v7ycix5hgnk750s1qgykyghl2mymhdkg39jrlk3kbj6xygq5b";
|
||||
url = "https://pub.dartlang.org/packages/frontend_server_client/versions/3.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
glob = fetchzip {
|
||||
sha256 = "0a6gbwsbz6rkg35dkff0zv88rvcflqdmda90hdfpn7jp1z1w9rhs";
|
||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.0.tar.gz";
|
||||
sha256 = "0ffab3azx8zkma36mk6wnig8bn8g5g0vjrq2gl21y77rxgw9iqxj";
|
||||
url = "https://pub.dartlang.org/packages/glob/versions/2.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
graphs = fetchzip {
|
||||
sha256 = "0cr6dgs1a7ln2ir5gd0kiwpn787lk4dwhqfjv8876hkkr1rv80m9";
|
||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.2.0.tar.gz";
|
||||
sha256 = "0fda0j8y6sq1rc9zpzglrzysl5h49y2ji1wq2lq0wx2c609dxm7f";
|
||||
url = "https://pub.dartlang.org/packages/graphs/versions/2.3.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -217,78 +205,78 @@
|
||||
};
|
||||
|
||||
io = fetchzip {
|
||||
sha256 = "1bp5l8hkrp6fjj7zw9af51hxyp52sjspc5558lq0lmi453l0czni";
|
||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.3.tar.gz";
|
||||
sha256 = "101kd0rw26vglmr1m5p130kbrp3k7dk4p5nr77wsbwgg53w8c0d4";
|
||||
url = "https://pub.dartlang.org/packages/io/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
js = fetchzip {
|
||||
sha256 = "13fbxgyg1v6bmzvxamg6494vk3923fn3mgxj6f4y476aqwk99n50";
|
||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.5.tar.gz";
|
||||
sha256 = "124a9yqrjdw3p4nnirab9hm9ziwraldlw4q5cb3sr0dcrli74qpw";
|
||||
url = "https://pub.dartlang.org/packages/js/versions/0.6.7.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_annotation = fetchzip {
|
||||
sha256 = "1p9nvn33psx2zbalhyqjw8gr4agd76jj5jq0fdz0i584c7l77bby";
|
||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.7.0.tar.gz";
|
||||
sha256 = "1jjw7p8qyqajgdq4jqvxipq5w0qrq9dpi1qmia70pk995akryh6m";
|
||||
url = "https://pub.dartlang.org/packages/json_annotation/versions/4.8.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
json_serializable = fetchzip {
|
||||
sha256 = "04d7laaxrbiybcgbv3y223hy8d6n9f84h5lv9sv79zd9ffzkb2hg";
|
||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.5.4.tar.gz";
|
||||
sha256 = "1pmidql9x6s2pbhdx9x20pwqwvwpfkvrz0h0cm1f8pqis76c90hb";
|
||||
url = "https://pub.dartlang.org/packages/json_serializable/versions/6.6.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
logging = fetchzip {
|
||||
sha256 = "0hl1mjh662c44ci7z60x92i0jsyqg1zm6k6fc89n9pdcxsqdpwfs";
|
||||
url = "https://pub.dartlang.org/packages/logging/versions/1.0.2.tar.gz";
|
||||
sha256 = "124hfjs66r30p92ndfmy5fymgy66yk9in97h8sq6fi7r78pqyc7g";
|
||||
url = "https://pub.dartlang.org/packages/logging/versions/1.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
matcher = fetchzip {
|
||||
sha256 = "0pjgc38clnjbv124n8bh724db1wcc4kk125j7dxl0icz7clvm0p0";
|
||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.13.tar.gz";
|
||||
sha256 = "0inznqkrxqnq09lcbwvda3xd07qfm1k3aa6dv1wy39gvci8hybss";
|
||||
url = "https://pub.dartlang.org/packages/matcher/versions/0.12.16.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
meta = fetchzip {
|
||||
sha256 = "01kqdd25nln5a219pr94s66p27m0kpqz0wpmwnm24kdy3ngif1v5";
|
||||
url = "https://pub.dartlang.org/packages/meta/versions/1.8.0.tar.gz";
|
||||
sha256 = "1l3zaz6q2s9mnm7s674xshsfqspy79p5kdbbnc99rf2l76avv4h3";
|
||||
url = "https://pub.dartlang.org/packages/meta/versions/1.9.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
mime = fetchzip {
|
||||
sha256 = "1dr3qikzvp10q1saka7azki5gk2kkf2v7k9wfqjsyxmza2zlv896";
|
||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.2.tar.gz";
|
||||
sha256 = "1dha9z64bsz8xhi0p62vmlyikr8xwbdlrw90hxghmm3rdgd9h25w";
|
||||
url = "https://pub.dartlang.org/packages/mime/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
moxlib = fetchzip {
|
||||
sha256 = "1j52xglpwy8c7dbylc3f6vrh0p52xhhwqs4h0qcqk8c1rvjn5czq";
|
||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.1.5.tar.gz";
|
||||
sha256 = "1qaacmcqhq33grn2nq8sn23ki62dcmw0fqy589xm1zv6w0pzfmsk";
|
||||
url = "https://git.polynom.me/api/packages/moxxy/pub/api/packages/moxlib/files/0.2.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
node_preamble = fetchzip {
|
||||
sha256 = "0i0gfc2yqa09182vc01lj47qpq98kfm9m8h4n8c5fby0mjd0lvyx";
|
||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.1.tar.gz";
|
||||
sha256 = "12ajg76r9aqmqkavvlxbnb3sszg1szcq3f30badkd0xc25mnhyh8";
|
||||
url = "https://pub.dartlang.org/packages/node_preamble/versions/2.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
omemo_dart = fetchzip {
|
||||
sha256 = "09x3jqa11hjdjp31nxnz91j6jssbc2f8a1lh44fmkc0d79hs8bbi";
|
||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.4.3.tar.gz";
|
||||
sha256 = "0fhf89ic5mdyld25l6rfb37a1fk1f0f2b4d72xi4r7pvr0ddjhz8";
|
||||
url = "https://git.polynom.me/api/packages/PapaTutuWawa/pub/api/packages/omemo_dart/files/0.5.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -301,8 +289,8 @@
|
||||
};
|
||||
|
||||
path = fetchzip {
|
||||
sha256 = "16ggdh29ciy7h8sdshhwmxn6dd12sfbykf2j82c56iwhhlljq181";
|
||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.2.tar.gz";
|
||||
sha256 = "1mjdhq2fsz6i9krhp2mnaks2bcw34sa4p7mg0v6njk8dgx2754iv";
|
||||
url = "https://pub.dartlang.org/packages/path/versions/1.8.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -315,8 +303,8 @@
|
||||
};
|
||||
|
||||
petitparser = fetchzip {
|
||||
sha256 = "1pqqqqiy9ald24qsi24q9qrr0zphgpsrnrv9rlx4vwr6xak7d8c0";
|
||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.1.0.tar.gz";
|
||||
sha256 = "19zqrpb1z77aw1k2s8rsxdfxczzv9934g2rdfj2jyiv3pqgdq8gh";
|
||||
url = "https://pub.dartlang.org/packages/petitparser/versions/5.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -335,16 +323,30 @@
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
protobuf = fetchzip {
|
||||
sha256 = "1jriyisf8bnvq5ygjk93mn2yzdlnii7xrhy6aabz54xr3y4dcy9x";
|
||||
url = "https://pub.dartlang.org/packages/protobuf/versions/2.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
protoc_plugin = fetchzip {
|
||||
sha256 = "0hjjd1xkv4s4g1d5n2aza0kdwlbfl2aivq99230m3yml7irn00jk";
|
||||
url = "https://pub.dartlang.org/packages/protoc_plugin/versions/20.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pub_semver = fetchzip {
|
||||
sha256 = "1vsj5c1f2dza4l5zmjix4zh65lp8gsg6pw01h57pijx2id0g4bwi";
|
||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.2.tar.gz";
|
||||
sha256 = "0wpcfz1crxipbjm18m71pl4vl2ra8vw1n93ff8snr54mmlyfb9z1";
|
||||
url = "https://pub.dartlang.org/packages/pub_semver/versions/2.1.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubspec_parse = fetchzip {
|
||||
sha256 = "19dmr9k4wsqjnhlzp1lbrw8dv7a1gnwmr8l5j9zlw407rmfg20d1";
|
||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.1.tar.gz";
|
||||
sha256 = "0dj8sf1w61g7vh1ly3sl690z0nwllzjzbapxswmgsglr0ndcyrs1";
|
||||
url = "https://pub.dartlang.org/packages/pubspec_parse/versions/1.2.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -364,43 +366,43 @@
|
||||
};
|
||||
|
||||
shelf = fetchzip {
|
||||
sha256 = "0x2xl7glrnq0hdxpy2i94a4wxbdrd6dm46hvhzgjn8alsm8z0wz1";
|
||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.0.tar.gz";
|
||||
sha256 = "10yk98nadrgj5d3r3241kdaywjjs1j10mg8gacv80kg1mhcfdrxp";
|
||||
url = "https://pub.dartlang.org/packages/shelf/versions/1.4.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_packages_handler = fetchzip {
|
||||
sha256 = "199rbdbifj46lg3iynznnsbs8zr4dfcw0s7wan8v73nvpqvli82q";
|
||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.1.tar.gz";
|
||||
sha256 = "1h8s42nff9ar0xn7yb42m64lpvmqzq8wranqrkkixdnp7w3pmv1x";
|
||||
url = "https://pub.dartlang.org/packages/shelf_packages_handler/versions/3.0.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_static = fetchzip {
|
||||
sha256 = "1kqbaslz7bna9lldda3ibrjg0gczbzlwgm9cic8shg0bnl0v3s34";
|
||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.1.tar.gz";
|
||||
sha256 = "1bcqynn2z2syrigmrclxgg8hjhd1x9742938i62cicbaga6vclaz";
|
||||
url = "https://pub.dartlang.org/packages/shelf_static/versions/1.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
shelf_web_socket = fetchzip {
|
||||
sha256 = "0rr87nx2wdf9alippxiidqlgi82fbprnsarr1jswg9qin0yy4jpn";
|
||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.3.tar.gz";
|
||||
sha256 = "110b5hrqwpnmq16shxxzjmcih5yfs5kh80dn8avfv0xj5iv7n94c";
|
||||
url = "https://pub.dartlang.org/packages/shelf_web_socket/versions/1.0.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_gen = fetchzip {
|
||||
sha256 = "1kxgx782lzpjhv736h0pz3lnxpcgiy05h0ysy0q77gix8q09i1hz";
|
||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.2.6.tar.gz";
|
||||
sha256 = "1jql5zccv4vnbbvwcpyyvz8l27pg1rviqbp4vrks5313nf4b0kjg";
|
||||
url = "https://pub.dartlang.org/packages/source_gen/versions/1.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_helper = fetchzip {
|
||||
sha256 = "044kzmzlfpx93s4raz5avijahizmvai0zvl0lbm4wi93ynhdp1pd";
|
||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.3.tar.gz";
|
||||
sha256 = "0mdd02vhcdcv9n58gzbx2q0bphwj0alz312ca1a8xpkf8jx3y8v4";
|
||||
url = "https://pub.dartlang.org/packages/source_helper/versions/1.3.4.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -413,29 +415,29 @@
|
||||
};
|
||||
|
||||
source_maps = fetchzip {
|
||||
sha256 = "18ixrlz3l2alk3hp0884qj0mcgzhxmjpg6nq0n1200pfy62pc4z6";
|
||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.11.tar.gz";
|
||||
sha256 = "004lcfka01agxjdw7zjhrffdkisvgx5s61b5gsl8qqk2jd1rswa7";
|
||||
url = "https://pub.dartlang.org/packages/source_maps/versions/0.10.12.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
source_span = fetchzip {
|
||||
sha256 = "1lq4sy7lw15qsv9cijf6l48p16qr19r7njzwr4pxn8vv1kh6rb86";
|
||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.9.1.tar.gz";
|
||||
sha256 = "1nybnf7l5chslp4fczhqnrgrhymy844lw7qrj6y08i626dshrd46";
|
||||
url = "https://pub.dartlang.org/packages/source_span/versions/1.10.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stack_trace = fetchzip {
|
||||
sha256 = "0bggqvvpkrfvqz24bnir4959k0c45azc3zivk4lyv3mvba6092na";
|
||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.0.tar.gz";
|
||||
sha256 = "0xpk2cvmgdh46iwip9jsb54fqx13jnina8pk03akxkmsxvag5izb";
|
||||
url = "https://pub.dartlang.org/packages/stack_trace/versions/1.11.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
stream_channel = fetchzip {
|
||||
sha256 = "054by84c60yxphr3qgg6f82gg6d22a54aqjp265anlm8dwz1ji32";
|
||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.1.tar.gz";
|
||||
sha256 = "0nrlw6zcscgnn6818krkbgs9qiv3f7q8pa7ljw1bqkrsb7xabm8s";
|
||||
url = "https://pub.dartlang.org/packages/stream_channel/versions/2.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -455,8 +457,8 @@
|
||||
};
|
||||
|
||||
synchronized = fetchzip {
|
||||
sha256 = "1j6108cq1hbcqpwhk9sah8q3gcidd7222bzhha2nk9syxhzqy82i";
|
||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.0.0%2B2.tar.gz";
|
||||
sha256 = "1fx1z1p5qkn4qnq24riw5s86vmq645ppg8f74iyv2fc9rvr301ar";
|
||||
url = "https://pub.dartlang.org/packages/synchronized/versions/3.1.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -469,36 +471,36 @@
|
||||
};
|
||||
|
||||
test = fetchzip {
|
||||
sha256 = "08kimbjvkdw3bkj7za36p3yqdr8dnlb5v30c250kvdncb7k09h4x";
|
||||
url = "https://pub.dartlang.org/packages/test/versions/1.22.0.tar.gz";
|
||||
sha256 = "002phlj2pg6nll5hv449izxbqk29zwmwc77d0jx2iimz18dgy2r5";
|
||||
url = "https://pub.dartlang.org/packages/test/versions/1.24.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_api = fetchzip {
|
||||
sha256 = "0mfyjpqkkmaqdh7xygrydx12591wq9ll816f61n80dc6rmkdx7px";
|
||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.4.16.tar.gz";
|
||||
sha256 = "0as1xcywjrd2zax3cm56qmnac12shf8c1ynnzzjwnggm23f61dxb";
|
||||
url = "https://pub.dartlang.org/packages/test_api/versions/0.6.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
test_core = fetchzip {
|
||||
sha256 = "1r8dnvkxxvh55z1c8lrsja1m0dkf5i4lgwwqixcx0mqvxx5w3005";
|
||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.4.20.tar.gz";
|
||||
sha256 = "1cx2rmz1xzk5z5yh8fpbsrsz4mgjanrw4xvnp0qzdnm2d7vhaq0y";
|
||||
url = "https://pub.dartlang.org/packages/test_core/versions/0.5.3.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
timing = fetchzip {
|
||||
sha256 = "0a02znvy0fbzr0n4ai67pp8in7w6m768aynkk1kp5lnmgy17ppsg";
|
||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.0.tar.gz";
|
||||
sha256 = "15jvxsw7v0gwbdlykma60l1qlhlzb3brh6m0sg2bgbfir4l5s9gw";
|
||||
url = "https://pub.dartlang.org/packages/timing/versions/1.0.1.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
typed_data = fetchzip {
|
||||
sha256 = "1x402bvyzdmdvmyqhyfamjxf54p9j8sa8ns2n5dwsdhnfqbw859g";
|
||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.1.tar.gz";
|
||||
sha256 = "0q6ggc52vfpr8kqaq69h757wy942hvgshhnsr2pjdinb4zk2sxl1";
|
||||
url = "https://pub.dartlang.org/packages/typed_data/versions/1.3.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -511,8 +513,8 @@
|
||||
};
|
||||
|
||||
uuid = fetchzip {
|
||||
sha256 = "12lsynr07lw9848jknmzxvzn3ia12xdj07iiva0vg0qjvpq7ladg";
|
||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.5.tar.gz";
|
||||
sha256 = "1nh1hxfr6bhyadqqcxrpwrphmm75f1iq4rzfjdwa2486xwlh7vx3";
|
||||
url = "https://pub.dartlang.org/packages/uuid/versions/3.0.7.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -525,8 +527,8 @@
|
||||
};
|
||||
|
||||
vm_service = fetchzip {
|
||||
sha256 = "05xaxaxzyfls6jklw1hzws2jmina1cjk10gbl7a63djh1ghnzjb5";
|
||||
url = "https://pub.dartlang.org/packages/vm_service/versions/9.4.0.tar.gz";
|
||||
sha256 = "15ail7rbaq9ksg73cc0mw2k5imbiidl95yfd4v49k81gp5xmj92w";
|
||||
url = "https://pub.dartlang.org/packages/vm_service/versions/11.10.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -539,8 +541,8 @@
|
||||
};
|
||||
|
||||
web_socket_channel = fetchzip {
|
||||
sha256 = "147amn05v1f1a1grxjr7yzgshrczjwijwiywggsv6dgic8kxyj5a";
|
||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.2.0.tar.gz";
|
||||
sha256 = "0f9441c4zifb5qadpjg319dcilimpkdhfacnkl543802bf8qjn4w";
|
||||
url = "https://pub.dartlang.org/packages/web_socket_channel/versions/2.4.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
@@ -553,262 +555,262 @@
|
||||
};
|
||||
|
||||
xml = fetchzip {
|
||||
sha256 = "0jwknkfcnb5svg6r01xjsj0aiw06mlx54pgay1ymaaqm2mjhyz01";
|
||||
url = "https://pub.dartlang.org/packages/xml/versions/6.2.0.tar.gz";
|
||||
sha256 = "120azx71gazvrrn07vd83vrffzrhsqnmf9rdjxl73rra9py8ixiy";
|
||||
url = "https://pub.dartlang.org/packages/xml/versions/6.3.0.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
yaml = fetchzip {
|
||||
sha256 = "0mqqmzn3c9rr38b5xm312fz1vyp6vb36lm477r9hak77bxzpp0iw";
|
||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.1.tar.gz";
|
||||
sha256 = "0awh9dynbhrlq8zgszaiyxbyyn9b6wyps1zww4z2lx62nbma0pda";
|
||||
url = "https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz";
|
||||
stripRoot = false;
|
||||
extension = "tar.gz";
|
||||
};
|
||||
|
||||
pubCache = runCommand "moxxmpp-pub-cache" {} ''
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dartlang.org/_fe_analyzer_shared-50.0.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${_fe_analyzer_shared} $out/hosted/pub.dev/_fe_analyzer_shared-61.0.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${analyzer} $out/hosted/pub.dartlang.org/analyzer-5.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${analyzer} $out/hosted/pub.dev/analyzer-5.13.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${args} $out/hosted/pub.dartlang.org/args-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${args} $out/hosted/pub.dev/args-2.4.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${async} $out/hosted/pub.dartlang.org/async-2.10.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${async} $out/hosted/pub.dev/async-2.11.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${boolean_selector} $out/hosted/pub.dartlang.org/boolean_selector-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${boolean_selector} $out/hosted/pub.dev/boolean_selector-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build} $out/hosted/pub.dartlang.org/build-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build} $out/hosted/pub.dev/build-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_config} $out/hosted/pub.dartlang.org/build_config-1.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_config} $out/hosted/pub.dev/build_config-1.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_daemon} $out/hosted/pub.dartlang.org/build_daemon-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_daemon} $out/hosted/pub.dev/build_daemon-3.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_resolvers} $out/hosted/pub.dartlang.org/build_resolvers-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_resolvers} $out/hosted/pub.dev/build_resolvers-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner} $out/hosted/pub.dartlang.org/build_runner-2.3.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_runner} $out/hosted/pub.dev/build_runner-2.3.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${build_runner_core} $out/hosted/pub.dartlang.org/build_runner_core-7.2.7
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${build_runner_core} $out/hosted/pub.dev/build_runner_core-7.2.7+1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_collection} $out/hosted/pub.dartlang.org/built_collection-5.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${built_collection} $out/hosted/pub.dev/built_collection-5.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${built_value} $out/hosted/pub.dartlang.org/built_value-8.4.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${built_value} $out/hosted/pub.dev/built_value-8.6.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${checked_yaml} $out/hosted/pub.dartlang.org/checked_yaml-2.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${checked_yaml} $out/hosted/pub.dev/checked_yaml-2.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${code_builder} $out/hosted/pub.dartlang.org/code_builder-4.3.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${code_builder} $out/hosted/pub.dev/code_builder-4.6.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${collection} $out/hosted/pub.dartlang.org/collection-1.17.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${collection} $out/hosted/pub.dev/collection-1.18.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${convert} $out/hosted/pub.dartlang.org/convert-3.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${convert} $out/hosted/pub.dev/convert-3.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${coverage} $out/hosted/pub.dartlang.org/coverage-1.6.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${coverage} $out/hosted/pub.dev/coverage-1.6.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${crypto} $out/hosted/pub.dartlang.org/crypto-3.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${crypto} $out/hosted/pub.dev/crypto-3.0.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${cryptography} $out/hosted/pub.dartlang.org/cryptography-2.0.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${cryptography} $out/hosted/pub.dev/cryptography-2.5.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${dart_style} $out/hosted/pub.dartlang.org/dart_style-2.2.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${dart_style} $out/hosted/pub.dev/dart_style-2.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${file} $out/hosted/pub.dartlang.org/file-6.1.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${file} $out/hosted/pub.dev/file-6.1.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${fixnum} $out/hosted/pub.dartlang.org/fixnum-1.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${fixnum} $out/hosted/pub.dev/fixnum-1.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed} $out/hosted/pub.dartlang.org/freezed-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${frontend_server_client} $out/hosted/pub.dev/frontend_server_client-3.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${freezed_annotation} $out/hosted/pub.dartlang.org/freezed_annotation-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${glob} $out/hosted/pub.dev/glob-2.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${frontend_server_client} $out/hosted/pub.dartlang.org/frontend_server_client-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${graphs} $out/hosted/pub.dev/graphs-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${glob} $out/hosted/pub.dartlang.org/glob-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${hex} $out/hosted/pub.dev/hex-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${graphs} $out/hosted/pub.dartlang.org/graphs-2.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${http_multi_server} $out/hosted/pub.dev/http_multi_server-3.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${hex} $out/hosted/pub.dartlang.org/hex-0.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${http_parser} $out/hosted/pub.dev/http_parser-4.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_multi_server} $out/hosted/pub.dartlang.org/http_multi_server-3.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${io} $out/hosted/pub.dev/io-1.0.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${http_parser} $out/hosted/pub.dartlang.org/http_parser-4.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${js} $out/hosted/pub.dev/js-0.6.7
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${io} $out/hosted/pub.dartlang.org/io-1.0.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${json_annotation} $out/hosted/pub.dev/json_annotation-4.8.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${js} $out/hosted/pub.dartlang.org/js-0.6.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${json_serializable} $out/hosted/pub.dev/json_serializable-6.6.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_annotation} $out/hosted/pub.dartlang.org/json_annotation-4.7.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${logging} $out/hosted/pub.dev/logging-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${json_serializable} $out/hosted/pub.dartlang.org/json_serializable-6.5.4
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${matcher} $out/hosted/pub.dev/matcher-0.12.16
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${logging} $out/hosted/pub.dartlang.org/logging-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${meta} $out/hosted/pub.dev/meta-1.9.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${matcher} $out/hosted/pub.dartlang.org/matcher-0.12.13
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${meta} $out/hosted/pub.dartlang.org/meta-1.8.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${mime} $out/hosted/pub.dartlang.org/mime-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${mime} $out/hosted/pub.dev/mime-1.0.4
|
||||
|
||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47
|
||||
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.1.5
|
||||
ln -s ${moxlib} $out/hosted/git.polynom.me%47api%47packages%47Moxxy%47pub%47/moxlib-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${node_preamble} $out/hosted/pub.dartlang.org/node_preamble-2.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${node_preamble} $out/hosted/pub.dev/node_preamble-2.0.2
|
||||
|
||||
mkdir -p $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47
|
||||
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.4.3
|
||||
ln -s ${omemo_dart} $out/hosted/git.polynom.me%47api%47packages%47PapaTutuWawa%47pub%47/omemo_dart-0.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${package_config} $out/hosted/pub.dartlang.org/package_config-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${package_config} $out/hosted/pub.dev/package_config-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${path} $out/hosted/pub.dartlang.org/path-1.8.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${path} $out/hosted/pub.dev/path-1.8.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pedantic} $out/hosted/pub.dartlang.org/pedantic-1.11.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pedantic} $out/hosted/pub.dev/pedantic-1.11.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${petitparser} $out/hosted/pub.dartlang.org/petitparser-5.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${petitparser} $out/hosted/pub.dev/petitparser-5.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pinenacl} $out/hosted/pub.dartlang.org/pinenacl-0.5.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pinenacl} $out/hosted/pub.dev/pinenacl-0.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pool} $out/hosted/pub.dartlang.org/pool-1.5.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pool} $out/hosted/pub.dev/pool-1.5.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pub_semver} $out/hosted/pub.dartlang.org/pub_semver-2.1.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${protobuf} $out/hosted/pub.dev/protobuf-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${pubspec_parse} $out/hosted/pub.dartlang.org/pubspec_parse-1.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${protoc_plugin} $out/hosted/pub.dev/protoc_plugin-20.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${random_string} $out/hosted/pub.dartlang.org/random_string-2.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pub_semver} $out/hosted/pub.dev/pub_semver-2.1.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${saslprep} $out/hosted/pub.dartlang.org/saslprep-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${pubspec_parse} $out/hosted/pub.dev/pubspec_parse-1.2.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf} $out/hosted/pub.dartlang.org/shelf-1.4.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${random_string} $out/hosted/pub.dev/random_string-2.3.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dartlang.org/shelf_packages_handler-3.0.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${saslprep} $out/hosted/pub.dev/saslprep-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_static} $out/hosted/pub.dartlang.org/shelf_static-1.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf} $out/hosted/pub.dev/shelf-1.4.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${shelf_web_socket} $out/hosted/pub.dartlang.org/shelf_web_socket-1.0.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_packages_handler} $out/hosted/pub.dev/shelf_packages_handler-3.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_gen} $out/hosted/pub.dartlang.org/source_gen-1.2.6
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_static} $out/hosted/pub.dev/shelf_static-1.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_helper} $out/hosted/pub.dartlang.org/source_helper-1.3.3
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${shelf_web_socket} $out/hosted/pub.dev/shelf_web_socket-1.0.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dartlang.org/source_map_stack_trace-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_gen} $out/hosted/pub.dev/source_gen-1.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_maps} $out/hosted/pub.dartlang.org/source_maps-0.10.11
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_helper} $out/hosted/pub.dev/source_helper-1.3.4
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${source_span} $out/hosted/pub.dartlang.org/source_span-1.9.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_map_stack_trace} $out/hosted/pub.dev/source_map_stack_trace-2.1.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stack_trace} $out/hosted/pub.dartlang.org/stack_trace-1.11.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_maps} $out/hosted/pub.dev/source_maps-0.10.12
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_channel} $out/hosted/pub.dartlang.org/stream_channel-2.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${source_span} $out/hosted/pub.dev/source_span-1.10.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${stream_transform} $out/hosted/pub.dartlang.org/stream_transform-2.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stack_trace} $out/hosted/pub.dev/stack_trace-1.11.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${string_scanner} $out/hosted/pub.dartlang.org/string_scanner-1.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stream_channel} $out/hosted/pub.dev/stream_channel-2.1.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${synchronized} $out/hosted/pub.dartlang.org/synchronized-3.0.0+2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${stream_transform} $out/hosted/pub.dev/stream_transform-2.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${term_glyph} $out/hosted/pub.dartlang.org/term_glyph-1.2.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${string_scanner} $out/hosted/pub.dev/string_scanner-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test} $out/hosted/pub.dartlang.org/test-1.22.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${synchronized} $out/hosted/pub.dev/synchronized-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_api} $out/hosted/pub.dartlang.org/test_api-0.4.16
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${term_glyph} $out/hosted/pub.dev/term_glyph-1.2.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${test_core} $out/hosted/pub.dartlang.org/test_core-0.4.20
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test} $out/hosted/pub.dev/test-1.24.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${timing} $out/hosted/pub.dartlang.org/timing-1.0.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test_api} $out/hosted/pub.dev/test_api-0.6.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${typed_data} $out/hosted/pub.dartlang.org/typed_data-1.3.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${test_core} $out/hosted/pub.dev/test_core-0.5.3
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${unorm_dart} $out/hosted/pub.dartlang.org/unorm_dart-0.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${timing} $out/hosted/pub.dev/timing-1.0.1
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${uuid} $out/hosted/pub.dartlang.org/uuid-3.0.5
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${typed_data} $out/hosted/pub.dev/typed_data-1.3.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${very_good_analysis} $out/hosted/pub.dartlang.org/very_good_analysis-3.1.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${unorm_dart} $out/hosted/pub.dev/unorm_dart-0.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${vm_service} $out/hosted/pub.dartlang.org/vm_service-9.4.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${uuid} $out/hosted/pub.dev/uuid-3.0.7
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${watcher} $out/hosted/pub.dartlang.org/watcher-1.0.2
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${very_good_analysis} $out/hosted/pub.dev/very_good_analysis-3.1.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${web_socket_channel} $out/hosted/pub.dartlang.org/web_socket_channel-2.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${vm_service} $out/hosted/pub.dev/vm_service-11.10.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dartlang.org/webkit_inspection_protocol-1.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${watcher} $out/hosted/pub.dev/watcher-1.0.2
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${xml} $out/hosted/pub.dartlang.org/xml-6.2.0
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${web_socket_channel} $out/hosted/pub.dev/web_socket_channel-2.4.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dartlang.org
|
||||
ln -s ${yaml} $out/hosted/pub.dartlang.org/yaml-3.1.1
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${webkit_inspection_protocol} $out/hosted/pub.dev/webkit_inspection_protocol-1.2.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${xml} $out/hosted/pub.dev/xml-6.3.0
|
||||
|
||||
mkdir -p $out/hosted/pub.dev
|
||||
ln -s ${yaml} $out/hosted/pub.dev/yaml-3.1.2
|
||||
'';
|
||||
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
## 0.4.1
|
||||
- Moved FAST from staging to xep_0484.dart
|
||||
|
||||
## 0.4.0
|
||||
|
||||
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
|
||||
@@ -18,7 +21,14 @@
|
||||
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
|
||||
- **BREAKING**: Overriding `BaseOmemoManager` is no longer required. `OmemoManager` now takes callback methods instead.
|
||||
- Removed `ErrorResponseDiscoError` from the possible XEP-0030 errors.
|
||||
- **BREAKING**: Removed "Extensible File Thumbnails" (The `Thumbnail` type)
|
||||
- **BREAKING**: Removed "Extensible File Thumbnails" (The `Thumbnail` type).
|
||||
- *BREAKING*: Rename `UserAvatarManager`'s `getUserAvatar` to `getUserAvatarData`. It now also requires the id of the avatar to fetch
|
||||
- *BREAKING*: `UserAvatarManager`'s `getAvatarId` with `getLatestMetadata`.
|
||||
- The `PubSubManager` now supports PubSub's `max_items` in `getItems`.
|
||||
- *BREAKING*: `vCardManager`'s `VCardAvatarUpdatedEvent` no longer automatically requests the newest VCard avatar.
|
||||
- *BREAKING*: `XmppConnection` now tries to ensure that incoming data is processed in-order. The only exception are awaited stanzas as they are allowed to bypass the queue.
|
||||
- *BREAKING*: If a stanza handler causes an exception, the handler is simply skipped while processing.
|
||||
- Add better logging around what stanza handler is running and if they end processing early.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ export 'package:moxxmpp/src/socket.dart';
|
||||
export 'package:moxxmpp/src/stanza.dart';
|
||||
export 'package:moxxmpp/src/stringxml.dart';
|
||||
export 'package:moxxmpp/src/util/typed_map.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';
|
||||
@@ -47,6 +46,7 @@ export 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/events.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/xep_0045.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0054.dart';
|
||||
@@ -64,6 +64,7 @@ export 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0198/state.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0203.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0264.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0280.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0297.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||
@@ -94,3 +95,4 @@ export 'package:moxxmpp/src/xeps/xep_0447.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0449.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0484.dart';
|
||||
|
||||
@@ -1,35 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// A surrogate key for awaiting stanzas.
|
||||
@immutable
|
||||
class _StanzaSurrogateKey {
|
||||
const _StanzaSurrogateKey(this.sentTo, this.id, this.tag);
|
||||
/// (JID we sent a stanza to, the id of the sent stanza, the tag of the sent stanza).
|
||||
// ignore: avoid_private_typedef_functions
|
||||
typedef _StanzaCompositeKey = (String?, String, String);
|
||||
|
||||
/// The JID the original stanza was sent to. We expect the result to come from the
|
||||
/// same JID.
|
||||
final String sentTo;
|
||||
|
||||
/// The ID of the original stanza. We expect the result to have the same ID.
|
||||
final String id;
|
||||
|
||||
/// The tag name of the stanza.
|
||||
final String tag;
|
||||
|
||||
@override
|
||||
int get hashCode => sentTo.hashCode ^ id.hashCode ^ tag.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _StanzaSurrogateKey &&
|
||||
other.sentTo == sentTo &&
|
||||
other.id == id &&
|
||||
other.tag == tag;
|
||||
}
|
||||
}
|
||||
/// Callback function that returns the bare JID of the connection as a String.
|
||||
typedef GetBareJidCallback = String Function();
|
||||
|
||||
/// This class handles the await semantics for stanzas. Stanzas are given a "unique"
|
||||
/// key equal to the tuple (to, id, tag) with which their response is identified.
|
||||
@@ -40,8 +18,12 @@ class _StanzaSurrogateKey {
|
||||
///
|
||||
/// This class also handles some "edge cases" of RFC 6120, like an empty "from" attribute.
|
||||
class StanzaAwaiter {
|
||||
StanzaAwaiter(this._bareJidCallback);
|
||||
|
||||
final GetBareJidCallback _bareJidCallback;
|
||||
|
||||
/// The pending stanzas, identified by their surrogate key.
|
||||
final Map<_StanzaSurrogateKey, Completer<XMLNode>> _pending = {};
|
||||
final Map<_StanzaCompositeKey, Completer<XMLNode>> _pending = {};
|
||||
|
||||
/// The critical section for accessing [StanzaAwaiter._pending].
|
||||
final Lock _lock = Lock();
|
||||
@@ -52,30 +34,33 @@ class StanzaAwaiter {
|
||||
/// [tag] is the stanza's tag name.
|
||||
///
|
||||
/// Returns a future that might resolve to the response to the stanza.
|
||||
Future<Future<XMLNode>> addPending(String to, String id, String tag) async {
|
||||
Future<Future<XMLNode>> addPending(String? to, String id, String tag) async {
|
||||
// Check if we want to send a stanza to our bare JID and replace it with null.
|
||||
final processedTo = to != null && to == _bareJidCallback() ? null : to;
|
||||
|
||||
final completer = await _lock.synchronized(() {
|
||||
final completer = Completer<XMLNode>();
|
||||
_pending[_StanzaSurrogateKey(to, id, tag)] = completer;
|
||||
_pending[(processedTo, id, tag)] = completer;
|
||||
return completer;
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Checks if the stanza [stanza] is being awaited. [bareJid] is the bare JID of
|
||||
/// the connection.
|
||||
/// Checks if the stanza [stanza] is being awaited.
|
||||
/// If [stanza] is awaited, resolves the future and returns true. If not, returns
|
||||
/// false.
|
||||
Future<bool> onData(XMLNode stanza, JID bareJid) async {
|
||||
assert(bareJid.isBare(), 'bareJid must be bare');
|
||||
|
||||
Future<bool> onData(XMLNode stanza) async {
|
||||
final id = stanza.attributes['id'] as String?;
|
||||
if (id == null) return false;
|
||||
|
||||
final key = _StanzaSurrogateKey(
|
||||
// Section 8.1.2.1 § 3 of RFC 6120 says that an empty "from" indicates that the
|
||||
// attribute is implicitly from our own bare JID.
|
||||
stanza.attributes['from'] as String? ?? bareJid.toString(),
|
||||
// Check if we want to send a stanza to our bare JID and replace it with null.
|
||||
final from = stanza.attributes['from'] as String?;
|
||||
final processedFrom =
|
||||
from != null && from == _bareJidCallback() ? null : from;
|
||||
|
||||
final key = (
|
||||
processedFrom,
|
||||
id,
|
||||
stanza.tag,
|
||||
);
|
||||
@@ -91,4 +76,19 @@ class StanzaAwaiter {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Checks if [stanza] represents a stanza that is awaited. Returns true, if [stanza]
|
||||
/// is awaited. False, if not.
|
||||
Future<bool> isAwaited(XMLNode stanza) async {
|
||||
final id = stanza.attributes['id'] as String?;
|
||||
if (id == null) return false;
|
||||
|
||||
final key = (
|
||||
stanza.attributes['from'] as String?,
|
||||
id,
|
||||
stanza.tag,
|
||||
);
|
||||
|
||||
return _lock.synchronized(() => _pending.containsKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ import 'package:moxxmpp/src/settings.dart';
|
||||
import 'package:moxxmpp/src/socket.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/util/incoming_queue.dart';
|
||||
import 'package:moxxmpp/src/util/queue.dart';
|
||||
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0352.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// The states the XmppConnection can be in
|
||||
@@ -49,6 +49,19 @@ enum XmppConnectionState {
|
||||
error
|
||||
}
|
||||
|
||||
/// (The actual stanza handler, Name of the owning manager).
|
||||
typedef _StanzaHandlerWrapper = (StanzaHandler, String);
|
||||
|
||||
/// Wrapper around [stanzaHandlerSortComparator] for [_StanzaHandlerWrapper].
|
||||
int _stanzaHandlerWrapperSortComparator(
|
||||
_StanzaHandlerWrapper a,
|
||||
_StanzaHandlerWrapper b,
|
||||
) {
|
||||
final (ha, _) = a;
|
||||
final (hb, _) = b;
|
||||
return stanzaHandlerSortComparator(ha, hb);
|
||||
}
|
||||
|
||||
/// This class is a connection to the server.
|
||||
class XmppConnection {
|
||||
XmppConnection(
|
||||
@@ -58,7 +71,11 @@ class XmppConnection {
|
||||
this._socket, {
|
||||
this.connectingTimeout = const Duration(minutes: 2),
|
||||
}) : _reconnectionPolicy = reconnectionPolicy,
|
||||
_connectivityManager = connectivityManager {
|
||||
_connectivityManager = connectivityManager,
|
||||
assert(
|
||||
_socket.getDataStream().isBroadcast,
|
||||
"The socket's data stream must be a broadcast stream",
|
||||
) {
|
||||
// Allow the reconnection policy to perform reconnections by itself
|
||||
_reconnectionPolicy.register(
|
||||
_attemptReconnection,
|
||||
@@ -77,9 +94,15 @@ class XmppConnection {
|
||||
},
|
||||
);
|
||||
|
||||
_stanzaAwaiter = StanzaAwaiter(
|
||||
() => connectionSettings.jid.toBare().toString(),
|
||||
);
|
||||
_incomingStanzaQueue = IncomingStanzaQueue(handleXmlStream, _stanzaAwaiter);
|
||||
_socketStream = _socket.getDataStream();
|
||||
// TODO(Unknown): Handle on done
|
||||
_socketStream.transform(_streamParser).forEach(handleXmlStream);
|
||||
_socketStream
|
||||
.transform(_streamParser)
|
||||
.forEach(_incomingStanzaQueue.addStanza);
|
||||
_socketStream.listen(_handleOnDataCallbacks);
|
||||
_socket.getEventStream().listen(handleSocketEvent);
|
||||
|
||||
_stanzaQueue = AsyncStanzaQueue(
|
||||
@@ -109,16 +132,16 @@ class XmppConnection {
|
||||
final ConnectivityManager _connectivityManager;
|
||||
|
||||
/// A helper for handling await semantics with stanzas
|
||||
final StanzaAwaiter _stanzaAwaiter = StanzaAwaiter();
|
||||
late final StanzaAwaiter _stanzaAwaiter;
|
||||
|
||||
/// Sorted list of handlers that we call or incoming and outgoing stanzas
|
||||
final List<StanzaHandler> _incomingStanzaHandlers =
|
||||
final List<_StanzaHandlerWrapper> _incomingStanzaHandlers =
|
||||
List.empty(growable: true);
|
||||
final List<StanzaHandler> _incomingPreStanzaHandlers =
|
||||
final List<_StanzaHandlerWrapper> _incomingPreStanzaHandlers =
|
||||
List.empty(growable: true);
|
||||
final List<StanzaHandler> _outgoingPreStanzaHandlers =
|
||||
final List<_StanzaHandlerWrapper> _outgoingPreStanzaHandlers =
|
||||
List.empty(growable: true);
|
||||
final List<StanzaHandler> _outgoingPostStanzaHandlers =
|
||||
final List<_StanzaHandlerWrapper> _outgoingPostStanzaHandlers =
|
||||
List.empty(growable: true);
|
||||
final StreamController<XmppEvent> _eventStreamController =
|
||||
StreamController.broadcast();
|
||||
@@ -157,10 +180,6 @@ class XmppConnection {
|
||||
T? getNegotiatorById<T extends XmppFeatureNegotiatorBase>(String id) =>
|
||||
_negotiationsHandler.getNegotiatorById<T>(id);
|
||||
|
||||
/// Prevent data from being passed to _currentNegotiator.negotiator while the negotiator
|
||||
/// is still running.
|
||||
final Lock _negotiationLock = Lock();
|
||||
|
||||
/// The logger for the class
|
||||
final Logger _log = Logger('XmppConnection');
|
||||
|
||||
@@ -169,6 +188,8 @@ class XmppConnection {
|
||||
|
||||
bool get isAuthenticated => _isAuthenticated;
|
||||
|
||||
late final IncomingStanzaQueue _incomingStanzaQueue;
|
||||
|
||||
late final AsyncStanzaQueue _stanzaQueue;
|
||||
|
||||
/// Returns the JID we authenticate with and add the resource that we have bound.
|
||||
@@ -198,18 +219,25 @@ class XmppConnection {
|
||||
|
||||
_xmppManagers[manager.id] = manager;
|
||||
|
||||
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
|
||||
_incomingPreStanzaHandlers.addAll(manager.getIncomingPreStanzaHandlers());
|
||||
_outgoingPreStanzaHandlers.addAll(manager.getOutgoingPreStanzaHandlers());
|
||||
_outgoingPostStanzaHandlers
|
||||
.addAll(manager.getOutgoingPostStanzaHandlers());
|
||||
_incomingStanzaHandlers.addAll(
|
||||
manager.getIncomingStanzaHandlers().map((h) => (h, manager.name)),
|
||||
);
|
||||
_incomingPreStanzaHandlers.addAll(
|
||||
manager.getIncomingPreStanzaHandlers().map((h) => (h, manager.name)),
|
||||
);
|
||||
_outgoingPreStanzaHandlers.addAll(
|
||||
manager.getOutgoingPreStanzaHandlers().map((h) => (h, manager.name)),
|
||||
);
|
||||
_outgoingPostStanzaHandlers.addAll(
|
||||
manager.getOutgoingPostStanzaHandlers().map((h) => (h, manager.name)),
|
||||
);
|
||||
}
|
||||
|
||||
// Sort them
|
||||
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||
_incomingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||
_outgoingPreStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||
_outgoingPostStanzaHandlers.sort(stanzaHandlerSortComparator);
|
||||
_incomingStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||
_incomingPreStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||
_outgoingPreStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||
_outgoingPostStanzaHandlers.sort(_stanzaHandlerWrapperSortComparator);
|
||||
|
||||
// Run the post register callbacks
|
||||
for (final manager in _xmppManagers.values) {
|
||||
@@ -290,6 +318,13 @@ class XmppConnection {
|
||||
return getManagerById(csiManager);
|
||||
}
|
||||
|
||||
/// Called whenever we receive data on the socket.
|
||||
Future<void> _handleOnDataCallbacks(String _) async {
|
||||
for (final manager in _xmppManagers.values) {
|
||||
unawaited(manager.onData());
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to reconnect to the server by following an exponential backoff.
|
||||
Future<void> _attemptReconnection() async {
|
||||
_log.finest('_attemptReconnection: Setting state to notConnected');
|
||||
@@ -475,7 +510,7 @@ class XmppConnection {
|
||||
false,
|
||||
false,
|
||||
newStanza,
|
||||
TypedMap(),
|
||||
details.extensions ?? TypedMap(),
|
||||
encrypted: details.encrypted,
|
||||
shouldEncrypt: details.shouldEncrypt,
|
||||
forceEncryption: details.forceEncryption,
|
||||
@@ -515,7 +550,7 @@ class XmppConnection {
|
||||
// A stanza with no to attribute is for direct processing by the server. As such,
|
||||
// we can correlate it by just *assuming* we have that attribute
|
||||
// (RFC 6120 Section 8.1.1.1)
|
||||
data.stanza.to ?? connectionSettings.jid.toBare().toString(),
|
||||
data.stanza.to,
|
||||
data.stanza.id!,
|
||||
data.stanza.tag,
|
||||
)
|
||||
@@ -650,15 +685,30 @@ class XmppConnection {
|
||||
/// call its callback and end the processing if the callback returned true; continue
|
||||
/// if it returned false.
|
||||
Future<StanzaHandlerData> _runStanzaHandlers(
|
||||
List<StanzaHandler> handlers,
|
||||
List<_StanzaHandlerWrapper> handlers,
|
||||
Stanza stanza, {
|
||||
StanzaHandlerData? initial,
|
||||
}) async {
|
||||
var state = initial ?? StanzaHandlerData(false, false, stanza, TypedMap());
|
||||
for (final handler in handlers) {
|
||||
for (final handlerRaw in handlers) {
|
||||
final (handler, managerName) = handlerRaw;
|
||||
if (handler.matches(state.stanza)) {
|
||||
_log.finest(
|
||||
'Running handler for ${stanza.tag} (${stanza.attributes["id"]}) of $managerName',
|
||||
);
|
||||
try {
|
||||
state = await handler.callback(state.stanza, state);
|
||||
if (state.done || state.cancel) return state;
|
||||
} catch (ex) {
|
||||
_log.severe(
|
||||
'Handler from $managerName for ${stanza.tag} (${stanza.attributes["id"]}) failed with "$ex"',
|
||||
);
|
||||
}
|
||||
if (state.done || state.cancel) {
|
||||
_log.finest(
|
||||
'Processing ended early for ${stanza.tag} (${stanza.attributes["id"]}) by $managerName',
|
||||
);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,7 +793,6 @@ class XmppConnection {
|
||||
|
||||
final awaited = await _stanzaAwaiter.onData(
|
||||
incomingPreHandlers.stanza,
|
||||
connectionSettings.jid.toBare(),
|
||||
);
|
||||
if (awaited) {
|
||||
return;
|
||||
@@ -802,14 +851,12 @@ class XmppConnection {
|
||||
// causing (a) the negotiator to become confused and (b) the stanzas/nonzas to be
|
||||
// missed. This causes the data to wait while the negotiator is running and thus
|
||||
// prevent this issue.
|
||||
await _negotiationLock.synchronized(() async {
|
||||
if (_routingState != RoutingState.negotiating) {
|
||||
unawaited(handleXmlStream(event));
|
||||
return;
|
||||
}
|
||||
|
||||
await _negotiationsHandler.negotiate(event);
|
||||
});
|
||||
break;
|
||||
case RoutingState.handleStanzas:
|
||||
await _handleStanza(node);
|
||||
|
||||
@@ -184,16 +184,12 @@ class UserAvatarUpdatedEvent extends XmppEvent {
|
||||
class VCardAvatarUpdatedEvent extends XmppEvent {
|
||||
VCardAvatarUpdatedEvent(
|
||||
this.jid,
|
||||
this.base64,
|
||||
this.hash,
|
||||
);
|
||||
|
||||
/// The JID of the entity that updated their avatar.
|
||||
final JID jid;
|
||||
|
||||
/// The base64-encoded avatar data.
|
||||
final String base64;
|
||||
|
||||
/// The SHA-1 hash of the avatar.
|
||||
final String hash;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ abstract class XmppManagerBase {
|
||||
/// handler's priority, the earlier it is run.
|
||||
List<NonzaHandler> getNonzaHandlers() => [];
|
||||
|
||||
/// Whenever the socket receives data, this method is called, if it is non-null.
|
||||
Future<void> onData() async {}
|
||||
|
||||
/// Return a list of features that should be included in a disco response.
|
||||
List<String> getDiscoFeatures() => [];
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class MessageManager extends XmppManagerBase {
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessage,
|
||||
priority: messageHandlerPriority,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -117,6 +117,7 @@ class MessageManager extends XmppManagerBase {
|
||||
.flattened
|
||||
.toList(),
|
||||
),
|
||||
extensions: extensions,
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
|
||||
|
||||
// XEP-0004
|
||||
const dataFormsXmlns = 'jabber:x:data';
|
||||
const formVarFormType = 'FORM_TYPE';
|
||||
|
||||
// XEP-0030
|
||||
const discoInfoXmlns = 'http://jabber.org/protocol/disco#info';
|
||||
@@ -23,6 +24,8 @@ const extendedAddressingXmlns = 'http://jabber.org/protocol/address';
|
||||
|
||||
// XEP-0045
|
||||
const mucXmlns = 'http://jabber.org/protocol/muc';
|
||||
const mucUserXmlns = 'http://jabber.org/protocol/muc#user';
|
||||
const roomInfoFormType = 'http://jabber.org/protocol/muc#roominfo';
|
||||
|
||||
// XEP-0054
|
||||
const vCardTempXmlns = 'vcard-temp';
|
||||
@@ -163,7 +166,6 @@ const stickersXmlns = 'urn:xmpp:stickers:0';
|
||||
|
||||
// XEP-0461
|
||||
const replyXmlns = 'urn:xmpp:reply:0';
|
||||
const fallbackXmlns = 'urn:xmpp:feature-fallback:0';
|
||||
|
||||
// ???
|
||||
const urlDataXmlns = 'http://jabber.org/protocol/url-data';
|
||||
|
||||
@@ -57,9 +57,10 @@ class _ChunkedConversionBuffer<S, T> {
|
||||
}
|
||||
|
||||
/// A buffer to put between a socket's input and a full XML stream.
|
||||
class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
||||
final StreamController<XMPPStreamObject> _streamController =
|
||||
StreamController<XMPPStreamObject>();
|
||||
class XMPPStreamParser
|
||||
extends StreamTransformerBase<String, List<XMPPStreamObject>> {
|
||||
final StreamController<List<XMPPStreamObject>> _streamController =
|
||||
StreamController<List<XMPPStreamObject>>();
|
||||
|
||||
/// Turns a String into a list of [XmlEvent]s in a chunked fashion.
|
||||
_ChunkedConversionBuffer<String, XmlEvent> _eventBuffer =
|
||||
@@ -117,13 +118,14 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<XMPPStreamObject> bind(Stream<String> stream) {
|
||||
Stream<List<XMPPStreamObject>> bind(Stream<String> stream) {
|
||||
// We do not want to use xml's toXmlEvents and toSubtreeEvents methods as they
|
||||
// create streams we cannot close. We need to be able to destroy and recreate an
|
||||
// XML parser whenever we start a new connection.
|
||||
stream.listen((input) {
|
||||
final events = _eventBuffer.convert(input);
|
||||
final streamHeaderEvents = _streamHeaderSelector.convert(events);
|
||||
final objects = List<XMPPStreamObject>.empty(growable: true);
|
||||
|
||||
// Process the stream header separately.
|
||||
for (final event in streamHeaderEvents) {
|
||||
@@ -135,7 +137,7 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
||||
continue;
|
||||
}
|
||||
|
||||
_streamController.add(
|
||||
objects.add(
|
||||
XMPPStreamHeader(
|
||||
Map<String, String>.fromEntries(
|
||||
event.attributes.map((attr) {
|
||||
@@ -151,13 +153,15 @@ class XMPPStreamParser extends StreamTransformerBase<String, XMPPStreamObject> {
|
||||
final children = _childBuffer.convert(childEvents);
|
||||
for (final node in children) {
|
||||
if (node.nodeType == XmlNodeType.ELEMENT) {
|
||||
_streamController.add(
|
||||
objects.add(
|
||||
XMPPStreamElement(
|
||||
XMLNode.fromXmlElement(node as XmlElement),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_streamController.add(objects);
|
||||
});
|
||||
|
||||
return _streamController.stream;
|
||||
|
||||
@@ -66,7 +66,7 @@ class PresenceManager extends XmppManagerBase {
|
||||
stanzaTag: 'presence',
|
||||
callback: _onPresence,
|
||||
priority: presenceHandlerPriority,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -96,7 +96,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
if (pickedForSasl2) {
|
||||
state = NegotiatorState.done;
|
||||
}
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -246,6 +246,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
bool _checkSignature(String base64Signature) {
|
||||
final signature =
|
||||
parseKeyValue(utf8.decode(base64.decode(base64Signature)));
|
||||
_log.finest(
|
||||
'Expecting signature: "$_serverSignature", got: "${signature["v"]}"',
|
||||
);
|
||||
return signature['v']! == _serverSignature;
|
||||
}
|
||||
|
||||
@@ -360,6 +363,11 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
|
||||
|
||||
@override
|
||||
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
|
||||
// Don't do anything if we have not been picked for SASL2.
|
||||
if (!pickedForSasl2) {
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
// When we're done with SASL2, check the additional data to verify the server
|
||||
// signature.
|
||||
state = NegotiatorState.done;
|
||||
|
||||
@@ -122,7 +122,7 @@ class RosterManager extends XmppManagerBase {
|
||||
tagName: 'query',
|
||||
tagXmlns: rosterXmlns,
|
||||
callback: _onRosterPush,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -182,8 +182,18 @@ class RosterManager extends XmppManagerBase {
|
||||
|
||||
/// Shared code between requesting rosters without and with roster versioning, if
|
||||
/// the server deems a regular roster response more efficient than n roster pushes.
|
||||
///
|
||||
/// [query] is the <query /> child of the iq, if available.
|
||||
///
|
||||
/// If roster versioning was used, then [requestedRosterVersion] is the version
|
||||
/// we requested the roster with.
|
||||
///
|
||||
/// Note that if roster versioning is used and the server returns us an empty iq,
|
||||
/// it means that the roster did not change since the last version. In that case,
|
||||
/// we do nothing and just return. The roster state manager will not be notified.
|
||||
Future<Result<RosterRequestResult, RosterError>> _handleRosterResponse(
|
||||
XMLNode? query,
|
||||
String? requestedRosterVersion,
|
||||
) async {
|
||||
final List<XmppRosterItem> items;
|
||||
String? rosterVersion;
|
||||
@@ -204,6 +214,14 @@ class RosterManager extends XmppManagerBase {
|
||||
.toList();
|
||||
|
||||
rosterVersion = query.attributes['ver'] as String?;
|
||||
} else if (requestedRosterVersion != null) {
|
||||
// Skip the handleRosterFetch call since nothing changed.
|
||||
return Result(
|
||||
RosterRequestResult(
|
||||
[],
|
||||
requestedRosterVersion,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.warning(
|
||||
'Server response to roster request without roster versioning does not contain a <query /> element, while the type is not error. This violates RFC6121',
|
||||
@@ -258,7 +276,7 @@ class RosterManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final responseQuery = response.firstTag('query', xmlns: rosterXmlns);
|
||||
return _handleRosterResponse(responseQuery);
|
||||
return _handleRosterResponse(responseQuery, rosterVersion);
|
||||
}
|
||||
|
||||
/// Requests a series of roster pushes according to RFC6121. Requires that the server
|
||||
@@ -266,6 +284,7 @@ class RosterManager extends XmppManagerBase {
|
||||
Future<Result<RosterRequestResult?, RosterError>>
|
||||
requestRosterPushes() async {
|
||||
final attrs = getAttributes();
|
||||
final rosterVersion = await _stateManager.getRosterVersion();
|
||||
final result = (await attrs.sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
@@ -275,9 +294,9 @@ class RosterManager extends XmppManagerBase {
|
||||
tag: 'query',
|
||||
xmlns: rosterXmlns,
|
||||
attributes: {
|
||||
'ver': await _stateManager.getRosterVersion() ?? '',
|
||||
'ver': rosterVersion ?? '',
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -289,7 +308,7 @@ class RosterManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final query = result.firstTag('query', xmlns: rosterXmlns);
|
||||
return _handleRosterResponse(query);
|
||||
return _handleRosterResponse(query, rosterVersion);
|
||||
}
|
||||
|
||||
bool rosterVersioningAvailable() {
|
||||
@@ -319,14 +338,12 @@ class RosterManager extends XmppManagerBase {
|
||||
tag: 'item',
|
||||
attributes: <String, String>{
|
||||
'jid': jid,
|
||||
...title == jid.split('@')[0]
|
||||
? <String, String>{}
|
||||
: <String, String>{'name': title}
|
||||
if (title == jid.split('@')[0]) 'name': title,
|
||||
},
|
||||
children: (groups ?? [])
|
||||
.map((group) => XMLNode(tag: 'group', text: group))
|
||||
.toList(),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -357,13 +374,13 @@ class RosterManager extends XmppManagerBase {
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{
|
||||
attributes: {
|
||||
'jid': jid,
|
||||
'subscription': 'remove'
|
||||
'subscription': 'remove',
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:moxxmpp/src/util/typed_map.dart';
|
||||
class StanzaDetails {
|
||||
const StanzaDetails(
|
||||
this.stanza, {
|
||||
this.extensions,
|
||||
this.addId = true,
|
||||
this.awaitable = true,
|
||||
this.shouldEncrypt = true,
|
||||
@@ -19,6 +20,9 @@ class StanzaDetails {
|
||||
/// The stanza to send.
|
||||
final Stanza stanza;
|
||||
|
||||
/// The extension data used for constructing the stanza.
|
||||
final TypedMap<StanzaHandlerExtension>? extensions;
|
||||
|
||||
/// Flag indicating whether a stanza id should be added before sending.
|
||||
final bool addId;
|
||||
|
||||
@@ -98,6 +102,8 @@ class RemoteServerTimeoutError extends StanzaError {
|
||||
/// An unknown error.
|
||||
class UnknownStanzaError extends StanzaError {}
|
||||
|
||||
const _stanzaNotDefined = Object();
|
||||
|
||||
class Stanza extends XMLNode {
|
||||
// ignore: use_super_parameters
|
||||
Stanza({
|
||||
@@ -212,7 +218,7 @@ class Stanza extends XMLNode {
|
||||
|
||||
Stanza copyWith({
|
||||
String? id,
|
||||
String? from,
|
||||
Object? from = _stanzaNotDefined,
|
||||
String? to,
|
||||
String? type,
|
||||
List<XMLNode>? children,
|
||||
@@ -221,7 +227,7 @@ class Stanza extends XMLNode {
|
||||
return Stanza(
|
||||
tag: tag,
|
||||
to: to ?? this.to,
|
||||
from: from ?? this.from,
|
||||
from: from != _stanzaNotDefined ? from as String? : this.from,
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
children: children ?? this.children,
|
||||
@@ -244,15 +250,14 @@ XMLNode buildErrorElement(String type, String condition, {String? text}) {
|
||||
XMLNode.xmlns(
|
||||
tag: condition,
|
||||
xmlns: fullStanzaXmlns,
|
||||
children: text != null
|
||||
? [
|
||||
children: [
|
||||
if (text != null)
|
||||
XMLNode.xmlns(
|
||||
tag: 'text',
|
||||
xmlns: fullStanzaXmlns,
|
||||
text: text,
|
||||
)
|
||||
]
|
||||
: [],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
97
packages/moxxmpp/lib/src/util/incoming_queue.dart
Normal file
97
packages/moxxmpp/lib/src/util/incoming_queue.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/src/awaiter.dart';
|
||||
import 'package:moxxmpp/src/parser.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// A queue for incoming [XMPPStreamObject]s to ensure "in order"
|
||||
/// processing (except for stanzas that are awaited).
|
||||
class IncomingStanzaQueue {
|
||||
IncomingStanzaQueue(this._callback, this._stanzaAwaiter);
|
||||
|
||||
/// The queue for storing the completer of each
|
||||
/// incoming stanza (or stream object to be precise).
|
||||
/// Only access while holding the lock [_lock].
|
||||
final Queue<Completer<void>> _queue = Queue();
|
||||
|
||||
/// Flag indicating whether a callback is already running (true)
|
||||
/// or not. "a callback" and not "the callback" because awaited stanzas
|
||||
/// are allowed to bypass the queue.
|
||||
/// Only access while holding the lock [_lock].
|
||||
bool _isRunning = false;
|
||||
|
||||
/// The function to call to process an incoming stream object.
|
||||
final Future<void> Function(XMPPStreamObject) _callback;
|
||||
|
||||
/// Lock guarding both [_queue] and [_isRunning].
|
||||
final Lock _lock = Lock();
|
||||
|
||||
/// Logger.
|
||||
final Logger _log = Logger('IncomingStanzaQueue');
|
||||
|
||||
final StanzaAwaiter _stanzaAwaiter;
|
||||
|
||||
Future<void> _processStreamObject(
|
||||
Future<void>? future,
|
||||
XMPPStreamObject object,
|
||||
) async {
|
||||
if (future == null) {
|
||||
if (object is XMPPStreamElement) {
|
||||
_log.finest(
|
||||
'Bypassing queue for ${object.node.tag} (${object.node.attributes["id"]})',
|
||||
);
|
||||
}
|
||||
return _callback(object);
|
||||
}
|
||||
|
||||
// Wait for our turn.
|
||||
await future;
|
||||
|
||||
// Run the callback.
|
||||
await _callback(object);
|
||||
|
||||
// Run the next entry.
|
||||
await _lock.synchronized(() {
|
||||
if (_queue.isNotEmpty) {
|
||||
_queue.removeFirst().complete();
|
||||
} else {
|
||||
_isRunning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addStanza(List<XMPPStreamObject> objects) async {
|
||||
await _lock.synchronized(() async {
|
||||
for (final object in objects) {
|
||||
if (await canBypassQueue(object)) {
|
||||
unawaited(
|
||||
_processStreamObject(null, object),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final completer = Completer<void>();
|
||||
if (_isRunning) {
|
||||
_queue.add(completer);
|
||||
} else {
|
||||
_isRunning = true;
|
||||
completer.complete();
|
||||
}
|
||||
|
||||
unawaited(
|
||||
_processStreamObject(completer.future, object),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> canBypassQueue(XMPPStreamObject object) async {
|
||||
if (object is XMPPStreamHeader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
object as XMPPStreamElement;
|
||||
return _stanzaAwaiter.isAwaited(object.node);
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ class DataFormOption {
|
||||
XMLNode toXml() {
|
||||
return XMLNode(
|
||||
tag: 'option',
|
||||
attributes: label != null
|
||||
? <String, dynamic>{'label': label}
|
||||
: <String, dynamic>{},
|
||||
attributes: {
|
||||
if (label != null) 'label': label,
|
||||
},
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'value',
|
||||
text: value,
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -45,19 +45,22 @@ class DataFormField {
|
||||
return XMLNode(
|
||||
tag: 'field',
|
||||
attributes: <String, dynamic>{
|
||||
...varAttr != null
|
||||
? <String, dynamic>{'var': varAttr}
|
||||
: <String, dynamic>{},
|
||||
...type != null ? <String, dynamic>{'type': type} : <String, dynamic>{},
|
||||
...label != null
|
||||
? <String, dynamic>{'label': label}
|
||||
: <String, dynamic>{}
|
||||
if (varAttr != null) 'var': varAttr,
|
||||
if (type != null) 'type': type,
|
||||
if (label != null) 'label': label,
|
||||
},
|
||||
children: [
|
||||
...description != null ? [XMLNode(tag: 'desc', text: description)] : [],
|
||||
...isRequired ? [XMLNode(tag: 'required')] : [],
|
||||
if (description != null)
|
||||
XMLNode(
|
||||
tag: 'desc',
|
||||
text: description,
|
||||
),
|
||||
if (isRequired)
|
||||
XMLNode(
|
||||
tag: 'required',
|
||||
),
|
||||
...values.map((value) => XMLNode(tag: 'value', text: value)),
|
||||
...options.map((option) => option.toXml())
|
||||
...options.map((option) => option.toXml()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@ Stanza buildDiscoInfoQueryStanza(JID entity, String? node) {
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: discoInfoXmlns,
|
||||
attributes: node != null ? {'node': node} : {},
|
||||
)
|
||||
attributes: {
|
||||
if (node != null) 'node': node,
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -27,8 +29,10 @@ Stanza buildDiscoItemsQueryStanza(JID entity, {String? node}) {
|
||||
XMLNode.xmlns(
|
||||
tag: 'query',
|
||||
xmlns: discoItemsXmlns,
|
||||
attributes: node != null ? {'node': node} : {},
|
||||
)
|
||||
attributes: {
|
||||
if (node != null) 'node': node,
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,13 +19,11 @@ class Identity {
|
||||
XMLNode toXMLNode() {
|
||||
return XMLNode(
|
||||
tag: 'identity',
|
||||
attributes: <String, dynamic>{
|
||||
attributes: {
|
||||
'category': category,
|
||||
'type': type,
|
||||
'name': name,
|
||||
...lang == null
|
||||
? <String, dynamic>{}
|
||||
: <String, dynamic>{'xml:lang': lang}
|
||||
if (lang != null) 'xml:lang': lang,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,3 +17,9 @@ class NoNicknameSpecified extends MUCError {}
|
||||
/// them to be a member of a room, but they are not currently joined to
|
||||
/// that room.
|
||||
class RoomNotJoinedError extends MUCError {}
|
||||
|
||||
/// Indicates that the MUC forbids us from joining, i.e. when we're banned.
|
||||
class JoinForbiddenError extends MUCError {}
|
||||
|
||||
/// Indicates that an unspecific error occurred while joining.
|
||||
class MUCUnspecificError extends MUCError {}
|
||||
|
||||
72
packages/moxxmpp/lib/src/xeps/xep_0045/events.dart
Normal file
72
packages/moxxmpp/lib/src/xeps/xep_0045/events.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||
|
||||
/// Triggered when the MUC changes our nickname.
|
||||
class OwnDataChangedEvent extends XmppEvent {
|
||||
OwnDataChangedEvent(
|
||||
this.roomJid,
|
||||
this.nick,
|
||||
this.affiliation,
|
||||
this.role,
|
||||
);
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// Our nickname.
|
||||
final String nick;
|
||||
|
||||
/// Our affiliation.
|
||||
final Affiliation affiliation;
|
||||
|
||||
/// Our role.
|
||||
final Role role;
|
||||
}
|
||||
|
||||
/// Triggered when an entity joins the MUC.
|
||||
class MemberJoinedEvent extends XmppEvent {
|
||||
MemberJoinedEvent(this.roomJid, this.member);
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// The new member.
|
||||
final RoomMember member;
|
||||
}
|
||||
|
||||
/// Triggered when an entity changes their presence in the MUC.
|
||||
class MemberChangedEvent extends XmppEvent {
|
||||
MemberChangedEvent(this.roomJid, this.member);
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// The new member.
|
||||
final RoomMember member;
|
||||
}
|
||||
|
||||
/// Triggered when an entity leaves the MUC.
|
||||
class MemberLeftEvent extends XmppEvent {
|
||||
MemberLeftEvent(this.roomJid, this.nick);
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// The nick of the user who left.
|
||||
final String nick;
|
||||
}
|
||||
|
||||
/// Triggered when an entity changes their nick.
|
||||
class MemberChangedNickEvent extends XmppEvent {
|
||||
MemberChangedNickEvent(this.roomJid, this.oldNick, this.newNick);
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// The original nick.
|
||||
final String oldNick;
|
||||
|
||||
/// The new nick.
|
||||
final String newNick;
|
||||
}
|
||||
2
packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart
Normal file
2
packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart
Normal file
@@ -0,0 +1,2 @@
|
||||
const selfPresenceStatus = '110';
|
||||
const nicknameChangedStatus = '303';
|
||||
@@ -1,12 +1,77 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
|
||||
class InvalidAffiliationException implements Exception {}
|
||||
|
||||
class InvalidRoleException implements Exception {}
|
||||
|
||||
enum Affiliation {
|
||||
owner('owner'),
|
||||
admin('admin'),
|
||||
member('member'),
|
||||
outcast('outcast'),
|
||||
none('none');
|
||||
|
||||
const Affiliation(this.value);
|
||||
|
||||
factory Affiliation.fromString(String value) {
|
||||
switch (value) {
|
||||
case 'owner':
|
||||
return Affiliation.owner;
|
||||
case 'admin':
|
||||
return Affiliation.admin;
|
||||
case 'member':
|
||||
return Affiliation.member;
|
||||
case 'outcast':
|
||||
return Affiliation.outcast;
|
||||
case 'none':
|
||||
return Affiliation.none;
|
||||
default:
|
||||
throw InvalidAffiliationException();
|
||||
}
|
||||
}
|
||||
|
||||
/// The value to use for an attribute referring to this affiliation.
|
||||
final String value;
|
||||
}
|
||||
|
||||
enum Role {
|
||||
moderator('moderator'),
|
||||
participant('participant'),
|
||||
visitor('visitor'),
|
||||
none('none');
|
||||
|
||||
const Role(this.value);
|
||||
|
||||
factory Role.fromString(String value) {
|
||||
switch (value) {
|
||||
case 'moderator':
|
||||
return Role.moderator;
|
||||
case 'participant':
|
||||
return Role.participant;
|
||||
case 'visitor':
|
||||
return Role.visitor;
|
||||
case 'none':
|
||||
return Role.none;
|
||||
default:
|
||||
throw InvalidRoleException();
|
||||
}
|
||||
}
|
||||
|
||||
/// The value to use for an attribute referring to this role.
|
||||
final String value;
|
||||
}
|
||||
|
||||
class RoomInformation {
|
||||
/// Represents information about a Multi-User Chat (MUC) room.
|
||||
RoomInformation({
|
||||
required this.jid,
|
||||
required this.features,
|
||||
required this.name,
|
||||
this.roomInfo,
|
||||
});
|
||||
|
||||
/// Constructs a [RoomInformation] object from a [DiscoInfo] object.
|
||||
@@ -21,6 +86,11 @@ class RoomInformation {
|
||||
name: discoInfo.identities
|
||||
.firstWhere((i) => i.category == 'conference')
|
||||
.name!,
|
||||
roomInfo: discoInfo.extendedInfo.firstWhereOrNull((form) {
|
||||
final field = form.getFieldByVar(formVarFormType);
|
||||
return field?.type == 'hidden' &&
|
||||
field?.values.first == roomInfoFormType;
|
||||
}),
|
||||
);
|
||||
|
||||
/// The JID of the Multi-User Chat (MUC) room.
|
||||
@@ -31,13 +101,63 @@ class RoomInformation {
|
||||
|
||||
/// The name or title of the Multi-User Chat (MUC) room.
|
||||
final String name;
|
||||
|
||||
/// The data form containing room information.
|
||||
final DataForm? roomInfo;
|
||||
}
|
||||
|
||||
/// The used message-id and an optional origin-id.
|
||||
typedef PendingMessage = (String, String?);
|
||||
|
||||
/// An entity inside a MUC room. The name "member" here does not refer to an affiliation of member.
|
||||
class RoomMember {
|
||||
const RoomMember(this.nick, this.affiliation, this.role);
|
||||
|
||||
/// The entity's nickname.
|
||||
final String nick;
|
||||
|
||||
/// The assigned affiliation.
|
||||
final Affiliation affiliation;
|
||||
|
||||
/// The assigned role.
|
||||
final Role role;
|
||||
|
||||
RoomMember copyWith({
|
||||
String? nick,
|
||||
Affiliation? affiliation,
|
||||
Role? role,
|
||||
}) {
|
||||
return RoomMember(
|
||||
nick ?? this.nick,
|
||||
affiliation ?? this.affiliation,
|
||||
role ?? this.role,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RoomState {
|
||||
RoomState({
|
||||
required this.roomJid,
|
||||
this.nick,
|
||||
});
|
||||
RoomState({required this.roomJid, this.nick, required this.joined}) {
|
||||
pendingMessages = List<PendingMessage>.empty(growable: true);
|
||||
}
|
||||
|
||||
/// The JID of the room.
|
||||
final JID roomJid;
|
||||
|
||||
/// The nick we're joined with.
|
||||
String? nick;
|
||||
|
||||
/// Flag whether we're joined and can process messages
|
||||
bool joined;
|
||||
|
||||
/// Our own affiliation inside the MUC.
|
||||
Affiliation? affiliation;
|
||||
|
||||
/// Our own role inside the MUC.
|
||||
Role? role;
|
||||
|
||||
/// The list of messages that we sent and are waiting for their echo.
|
||||
late final List<PendingMessage> pendingMessages;
|
||||
|
||||
/// "List" of entities inside the MUC.
|
||||
final Map<String, RoomMember> members = {};
|
||||
}
|
||||
|
||||
@@ -1,28 +1,129 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/events.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/data.dart';
|
||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/presence.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/events.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/status_codes.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
/// (Room JID, nickname)
|
||||
typedef MUCRoomJoin = (JID, String);
|
||||
|
||||
class MUCManager extends XmppManagerBase {
|
||||
MUCManager() : super(mucManager);
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
/// Map full JID to RoomState
|
||||
/// Map a room's JID to its RoomState
|
||||
final Map<JID, RoomState> _mucRoomCache = {};
|
||||
|
||||
/// Mapp a room's JID to a completer waiting for the completion of the join process.
|
||||
final Map<JID, Completer<Result<bool, MUCError>>> _mucRoomJoinCompleter = {};
|
||||
|
||||
/// Cache lock
|
||||
final Lock _cacheLock = Lock();
|
||||
|
||||
/// Flag indicating whether we joined the rooms added to the room list with
|
||||
/// [prepareRoomList].
|
||||
bool _joinedPreparedRooms = true;
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessage,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
),
|
||||
StanzaHandler(
|
||||
stanzaTag: 'presence',
|
||||
callback: _onPresence,
|
||||
tagName: 'x',
|
||||
tagXmlns: mucUserXmlns,
|
||||
// Before the PresenceManager
|
||||
priority: PresenceManager.presenceHandlerPriority + 1,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getOutgoingPreStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
stanzaTag: 'message',
|
||||
callback: _onMessageSent,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Future<void> onXmppEvent(XmppEvent event) async {
|
||||
if (event is! StreamNegotiationsDoneEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only attempt rejoining if we did not resume the stream and all
|
||||
// prepared rooms are already joined.
|
||||
if (event.resumed && _joinedPreparedRooms) {
|
||||
return;
|
||||
}
|
||||
|
||||
final mucJoins = List<MUCRoomJoin>.empty(growable: true);
|
||||
await _cacheLock.synchronized(() async {
|
||||
// Mark all groupchats as not joined.
|
||||
for (final jid in _mucRoomCache.keys) {
|
||||
_mucRoomCache[jid]!.joined = false;
|
||||
_mucRoomJoinCompleter[jid] = Completer();
|
||||
|
||||
// Re-join all MUCs.
|
||||
final state = _mucRoomCache[jid]!;
|
||||
mucJoins.add((jid, state.nick!));
|
||||
}
|
||||
});
|
||||
|
||||
for (final join in mucJoins) {
|
||||
final (jid, nick) = join;
|
||||
await _sendMucJoin(
|
||||
jid,
|
||||
nick,
|
||||
0,
|
||||
);
|
||||
}
|
||||
_joinedPreparedRooms = true;
|
||||
}
|
||||
|
||||
/// Prepares the internal room list to ensure that the rooms
|
||||
/// [rooms] are joined once we are connected.
|
||||
Future<void> prepareRoomList(List<MUCRoomJoin> rooms) async {
|
||||
assert(
|
||||
rooms.isNotEmpty,
|
||||
'The room list should not be empty',
|
||||
);
|
||||
|
||||
await _cacheLock.synchronized(() {
|
||||
_joinedPreparedRooms = false;
|
||||
for (final room in rooms) {
|
||||
final (roomJid, nick) = room;
|
||||
_mucRoomCache[roomJid] = RoomState(
|
||||
roomJid: roomJid,
|
||||
nick: nick,
|
||||
joined: false,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Queries the information of a Multi-User Chat room.
|
||||
///
|
||||
/// Retrieves the information about the specified MUC room by performing a
|
||||
@@ -43,7 +144,8 @@ class MUCManager extends XmppManagerBase {
|
||||
);
|
||||
return Result(roomInformation);
|
||||
} catch (e) {
|
||||
return Result(InvalidDiscoInfoResponse);
|
||||
logger.warning('Invalid disco information: $e');
|
||||
return Result(InvalidDiscoInfoResponse());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +157,37 @@ class MUCManager extends XmppManagerBase {
|
||||
/// if applicable.
|
||||
Future<Result<bool, MUCError>> joinRoom(
|
||||
JID roomJid,
|
||||
String nick,
|
||||
) async {
|
||||
String nick, {
|
||||
int? maxHistoryStanzas,
|
||||
}) async {
|
||||
if (nick.isEmpty) {
|
||||
return Result(NoNicknameSpecified());
|
||||
}
|
||||
|
||||
final completer =
|
||||
await _cacheLock.synchronized<Completer<Result<bool, MUCError>>>(
|
||||
() {
|
||||
_mucRoomCache[roomJid] = RoomState(
|
||||
roomJid: roomJid,
|
||||
nick: nick,
|
||||
joined: false,
|
||||
);
|
||||
|
||||
final completer = Completer<Result<bool, MUCError>>();
|
||||
_mucRoomJoinCompleter[roomJid] = completer;
|
||||
return completer;
|
||||
},
|
||||
);
|
||||
|
||||
await _sendMucJoin(roomJid, nick, maxHistoryStanzas);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<void> _sendMucJoin(
|
||||
JID roomJid,
|
||||
String nick,
|
||||
int? maxHistoryStanzas,
|
||||
) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
@@ -68,17 +196,21 @@ class MUCManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'x',
|
||||
xmlns: mucXmlns,
|
||||
)
|
||||
children: [
|
||||
if (maxHistoryStanzas != null)
|
||||
XMLNode(
|
||||
tag: 'history',
|
||||
attributes: {
|
||||
'maxstanzas': maxHistoryStanzas.toString(),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
await _cacheLock.synchronized(
|
||||
() {
|
||||
_mucRoomCache[roomJid] = RoomState(roomJid: roomJid, nick: nick);
|
||||
},
|
||||
);
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
/// Leaves a Multi-User Chat room.
|
||||
@@ -96,7 +228,7 @@ class MUCManager extends XmppManagerBase {
|
||||
return nick;
|
||||
});
|
||||
if (nick == null) {
|
||||
return Result(RoomNotJoinedError);
|
||||
return Result(RoomNotJoinedError());
|
||||
}
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
@@ -104,8 +236,277 @@ class MUCManager extends XmppManagerBase {
|
||||
to: roomJid.withResource(nick).toString(),
|
||||
type: 'unavailable',
|
||||
),
|
||||
awaitable: false,
|
||||
),
|
||||
);
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
Future<RoomState?> getRoomState(JID roomJid) async {
|
||||
return _cacheLock.synchronized(() => _mucRoomCache[roomJid]);
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onPresence(
|
||||
Stanza presence,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
if (presence.from == null) {
|
||||
logger.finest('Ignoring presence as it has no from attribute');
|
||||
return state;
|
||||
}
|
||||
|
||||
final from = JID.fromString(presence.from!);
|
||||
final bareFrom = from.toBare();
|
||||
return _cacheLock.synchronized(() {
|
||||
logger.finest('Lock aquired for presence from ${presence.from}');
|
||||
final room = _mucRoomCache[bareFrom];
|
||||
if (room == null) {
|
||||
logger.finest('Ignoring presence as it does not belong to a room');
|
||||
return state;
|
||||
}
|
||||
|
||||
if (from.resource.isEmpty) {
|
||||
// TODO(Unknown): Handle presence from the room itself.
|
||||
logger.finest('Ignoring presence as it has no resource');
|
||||
return state;
|
||||
}
|
||||
|
||||
if (presence.type == 'error') {
|
||||
final errorTag = presence.firstTag('error')!;
|
||||
final error = errorTag.firstTagByXmlns(fullStanzaXmlns)!;
|
||||
Result<bool, MUCError> result;
|
||||
if (error.tag == 'forbidden') {
|
||||
result = Result(JoinForbiddenError());
|
||||
} else {
|
||||
result = Result(MUCUnspecificError());
|
||||
}
|
||||
|
||||
_mucRoomCache.remove(bareFrom);
|
||||
_mucRoomJoinCompleter[bareFrom]!.complete(result);
|
||||
_mucRoomJoinCompleter.remove(bareFrom);
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
presence,
|
||||
state.extensions,
|
||||
);
|
||||
}
|
||||
|
||||
final x = presence.firstTag('x', xmlns: mucUserXmlns)!;
|
||||
final item = x.firstTag('item')!;
|
||||
final statuses = x
|
||||
.findTags('status')
|
||||
.map((s) => s.attributes['code']! as String)
|
||||
.toList();
|
||||
final role = Role.fromString(
|
||||
item.attributes['role']! as String,
|
||||
);
|
||||
final affiliation = Affiliation.fromString(
|
||||
item.attributes['affiliation']! as String,
|
||||
);
|
||||
|
||||
if (statuses.contains(selfPresenceStatus)) {
|
||||
if (room.joined) {
|
||||
if (room.nick != from.resource ||
|
||||
room.affiliation != affiliation ||
|
||||
room.role != role) {
|
||||
// Notify us of the changed data.
|
||||
getAttributes().sendEvent(
|
||||
OwnDataChangedEvent(
|
||||
bareFrom,
|
||||
from.resource,
|
||||
affiliation,
|
||||
role,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the data to make sure we're in sync with the MUC.
|
||||
room
|
||||
..nick = from.resource
|
||||
..affiliation = affiliation
|
||||
..role = role;
|
||||
logger.finest('Self-presence handled');
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
presence,
|
||||
state.extensions,
|
||||
);
|
||||
}
|
||||
|
||||
if (presence.attributes['type'] == 'unavailable') {
|
||||
if (role == Role.none) {
|
||||
// Cannot happen while joining, so we assume we are joined
|
||||
assert(
|
||||
room.joined,
|
||||
'Should not receive unavailable with role="none" while joining',
|
||||
);
|
||||
room.members.remove(from.resource);
|
||||
getAttributes().sendEvent(
|
||||
MemberLeftEvent(
|
||||
bareFrom,
|
||||
from.resource,
|
||||
),
|
||||
);
|
||||
} else if (statuses.contains(nicknameChangedStatus)) {
|
||||
assert(
|
||||
room.joined,
|
||||
'Should not receive nick change while joining',
|
||||
);
|
||||
final newNick = item.attributes['nick']! as String;
|
||||
final member = RoomMember(
|
||||
newNick,
|
||||
Affiliation.fromString(
|
||||
item.attributes['affiliation']! as String,
|
||||
),
|
||||
role,
|
||||
);
|
||||
|
||||
// Remove the old member.
|
||||
room.members.remove(from.resource);
|
||||
|
||||
// Add the "new" member".
|
||||
room.members[newNick] = member;
|
||||
|
||||
// Trigger an event.
|
||||
getAttributes().sendEvent(
|
||||
MemberChangedNickEvent(
|
||||
bareFrom,
|
||||
from.resource,
|
||||
newNick,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final member = RoomMember(
|
||||
from.resource,
|
||||
Affiliation.fromString(
|
||||
item.attributes['affiliation']! as String,
|
||||
),
|
||||
role,
|
||||
);
|
||||
logger.finest('Got presence from ${from.resource} in $bareFrom');
|
||||
if (room.joined) {
|
||||
if (room.members.containsKey(from.resource)) {
|
||||
getAttributes().sendEvent(
|
||||
MemberChangedEvent(
|
||||
bareFrom,
|
||||
member,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
getAttributes().sendEvent(
|
||||
MemberJoinedEvent(
|
||||
bareFrom,
|
||||
member,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
room.members[from.resource] = member;
|
||||
logger.finest('${from.resource} added to the member list');
|
||||
}
|
||||
|
||||
logger.finest('Ran through');
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
presence,
|
||||
state.extensions,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onMessageSent(
|
||||
Stanza message,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
if (message.to == null) {
|
||||
return state;
|
||||
}
|
||||
final toJid = JID.fromString(message.to!);
|
||||
|
||||
return _cacheLock.synchronized(() {
|
||||
if (!_mucRoomCache.containsKey(toJid)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
_mucRoomCache[toJid]!.pendingMessages.add(
|
||||
(message.id!, state.extensions.get<StableIdData>()?.originId),
|
||||
);
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
Stanza message,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
final fromJid = JID.fromString(message.from!);
|
||||
final roomJid = fromJid.toBare();
|
||||
return _cacheLock.synchronized(() {
|
||||
logger.finest('Lock aquired for message from ${message.from}');
|
||||
final roomState = _mucRoomCache[roomJid];
|
||||
if (roomState == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (message.type == 'groupchat' && message.firstTag('subject') != null) {
|
||||
// The room subject marks the end of the join flow.
|
||||
if (!roomState.joined) {
|
||||
// Mark the room as joined.
|
||||
_mucRoomCache[roomJid]!.joined = true;
|
||||
_mucRoomJoinCompleter[roomJid]!.complete(
|
||||
const Result(true),
|
||||
);
|
||||
_mucRoomJoinCompleter.remove(roomJid);
|
||||
logger.finest('$roomJid is now joined');
|
||||
}
|
||||
|
||||
// TODO(Unknown): Signal the subject?
|
||||
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
message,
|
||||
state.extensions,
|
||||
);
|
||||
} else {
|
||||
if (!roomState.joined) {
|
||||
// Ignore the discussion history.
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
message,
|
||||
state.extensions,
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this is the message reflection.
|
||||
if (message.id == null) {
|
||||
return state;
|
||||
}
|
||||
final pending =
|
||||
(message.id!, state.extensions.get<StableIdData>()?.originId);
|
||||
if (fromJid.resource == roomState.nick &&
|
||||
roomState.pendingMessages.contains(pending)) {
|
||||
// Silently drop the message.
|
||||
roomState.pendingMessages.remove(pending);
|
||||
|
||||
// TODO(Unknown): Maybe send an event stating that we received the reflection.
|
||||
return StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
message,
|
||||
state.extensions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class VCardManager extends XmppManagerBase {
|
||||
tagName: 'x',
|
||||
tagXmlns: vCardTempUpdate,
|
||||
callback: _onPresence,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -56,27 +56,13 @@ class VCardManager extends XmppManagerBase {
|
||||
final x = presence.firstTag('x', xmlns: vCardTempUpdate)!;
|
||||
final hash = x.firstTag('photo')!.innerText();
|
||||
|
||||
final from = JID.fromString(presence.from!).toBare();
|
||||
final lastHash = _lastHash[from];
|
||||
if (lastHash != hash) {
|
||||
_lastHash[from.toString()] = hash;
|
||||
final vcardResult = await requestVCard(from);
|
||||
|
||||
if (vcardResult.isType<VCard>()) {
|
||||
final binval = vcardResult.get<VCard>().photo?.binval;
|
||||
if (binval != null) {
|
||||
getAttributes().sendEvent(
|
||||
VCardAvatarUpdatedEvent(from, binval, hash),
|
||||
VCardAvatarUpdatedEvent(
|
||||
JID.fromString(presence.from!),
|
||||
hash,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.warning('No avatar data found');
|
||||
}
|
||||
} else {
|
||||
logger.warning('Failed to retrieve vCard for $from');
|
||||
}
|
||||
}
|
||||
|
||||
return state..done = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
VCardPhoto? _parseVCardPhoto(XMLNode? node) {
|
||||
@@ -108,7 +94,7 @@ class VCardManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'vCard',
|
||||
xmlns: vCardTempXmlns,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
encrypted: true,
|
||||
|
||||
@@ -38,26 +38,20 @@ class PubSubPublishOptions {
|
||||
varAttr: 'FORM_TYPE',
|
||||
type: 'hidden',
|
||||
),
|
||||
...accessModel != null
|
||||
? [
|
||||
if (accessModel != null)
|
||||
DataFormField(
|
||||
options: [],
|
||||
isRequired: false,
|
||||
values: [accessModel!],
|
||||
varAttr: 'pubsub#access_model',
|
||||
)
|
||||
]
|
||||
: [],
|
||||
...maxItems != null
|
||||
? [
|
||||
),
|
||||
if (maxItems != null)
|
||||
DataFormField(
|
||||
options: [],
|
||||
isRequired: false,
|
||||
values: [maxItems!],
|
||||
varAttr: 'pubsub#max_items',
|
||||
),
|
||||
]
|
||||
: [],
|
||||
],
|
||||
).toXml();
|
||||
}
|
||||
@@ -87,7 +81,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
tagName: 'event',
|
||||
tagXmlns: pubsubEventXmlns,
|
||||
callback: _onPubsubMessage,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -314,11 +308,11 @@ class PubSubManager extends XmppManagerBase {
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'item',
|
||||
attributes: id != null
|
||||
? <String, String>{'id': id}
|
||||
: <String, String>{},
|
||||
attributes: {
|
||||
if (id != null) 'id': id,
|
||||
},
|
||||
children: [payload],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
if (pubOptions != null)
|
||||
@@ -327,7 +321,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
children: [pubOptions.toXml()],
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
@@ -401,8 +395,9 @@ class PubSubManager extends XmppManagerBase {
|
||||
|
||||
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
||||
JID jid,
|
||||
String node,
|
||||
) async {
|
||||
String node, {
|
||||
int? maxItems,
|
||||
}) async {
|
||||
final result = (await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
@@ -415,10 +410,13 @@ class PubSubManager extends XmppManagerBase {
|
||||
children: [
|
||||
XMLNode(
|
||||
tag: 'items',
|
||||
attributes: <String, String>{'node': node},
|
||||
attributes: {
|
||||
'node': node,
|
||||
if (maxItems != null) 'max_items': maxItems.toString(),
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
shouldEncrypt: false,
|
||||
@@ -446,7 +444,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
Future<Result<PubSubError, PubSubItem>> getItem(
|
||||
String jid,
|
||||
JID jid,
|
||||
String node,
|
||||
String id,
|
||||
) async {
|
||||
@@ -454,7 +452,7 @@ class PubSubManager extends XmppManagerBase {
|
||||
StanzaDetails(
|
||||
Stanza.iq(
|
||||
type: 'get',
|
||||
to: jid,
|
||||
to: jid.toString(),
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'pubsub',
|
||||
|
||||
@@ -45,7 +45,7 @@ class OOBManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the message manager
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -5,10 +5,7 @@ import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.dart';
|
||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||
import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||
|
||||
@@ -43,11 +40,7 @@ class UserAvatarMetadata {
|
||||
);
|
||||
|
||||
factory UserAvatarMetadata.fromXML(XMLNode node) {
|
||||
assert(
|
||||
node.tag == 'metadata' &&
|
||||
node.attributes['xmlns'] == userAvatarMetadataXmlns,
|
||||
'<metadata /> element required',
|
||||
);
|
||||
assert(node.tag == 'info', 'node must be an <info /> element');
|
||||
|
||||
final width = node.attributes['width'] as String?;
|
||||
final height = node.attributes['height'] as String?;
|
||||
@@ -121,20 +114,44 @@ class UserAvatarManager extends XmppManagerBase {
|
||||
|
||||
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
||||
/// successful. Null otherwise
|
||||
Future<Result<AvatarError, UserAvatarData>> getUserAvatar(JID jid) async {
|
||||
Future<Result<AvatarError, UserAvatarData>> getUserAvatarData(
|
||||
JID jid,
|
||||
String id,
|
||||
) async {
|
||||
final pubsub = _getPubSubManager();
|
||||
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
||||
final resultRaw = await pubsub.getItem(jid, userAvatarDataXmlns, id);
|
||||
if (resultRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||
|
||||
final result = resultRaw.get<PubSubItem>();
|
||||
return Result(
|
||||
UserAvatarData(
|
||||
result.payload.innerText(),
|
||||
id,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Attempts to fetch the latest item from the User Avatar metadata node. Returns the list of
|
||||
/// metadata contained within it. The list may be empty.
|
||||
///
|
||||
/// If an error occured, returns an [AvatarError].
|
||||
Future<Result<AvatarError, List<UserAvatarMetadata>>> getLatestMetadata(
|
||||
JID jid,
|
||||
) async {
|
||||
final resultsRaw = await _getPubSubManager()
|
||||
.getItems(jid, userAvatarMetadataXmlns, maxItems: 1);
|
||||
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||
|
||||
final results = resultsRaw.get<List<PubSubItem>>();
|
||||
if (results.isEmpty) return Result(UnknownAvatarError());
|
||||
if (results.isEmpty) {
|
||||
return Result(UnknownAvatarError());
|
||||
}
|
||||
|
||||
final item = results[0];
|
||||
return Result(
|
||||
UserAvatarData(
|
||||
item.payload.innerText(),
|
||||
item.id,
|
||||
),
|
||||
results.first.payload
|
||||
.findTags('info')
|
||||
.map(UserAvatarMetadata.fromXML)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -216,21 +233,4 @@ class UserAvatarManager extends XmppManagerBase {
|
||||
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
/// Returns the PubSub Id of an avatar after doing a disco#items query.
|
||||
/// Note that this assumes that there is only one (1) item published on
|
||||
/// the node.
|
||||
Future<Result<AvatarError, String>> getAvatarId(JID jid) async {
|
||||
final disco = getAttributes().getManagerById(discoManager)! as DiscoManager;
|
||||
final response = await disco.discoItemsQuery(
|
||||
jid,
|
||||
node: userAvatarDataXmlns,
|
||||
);
|
||||
if (response.isType<StanzaError>()) return Result(UnknownAvatarError());
|
||||
|
||||
final items = response.get<List<DiscoItem>>();
|
||||
if (items.isEmpty) return Result(UnknownAvatarError());
|
||||
|
||||
return Result(items.first.name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class ChatStateManager extends XmppManagerBase {
|
||||
callback: _onChatStateReceived,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -173,39 +173,20 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
});
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<StanzaHandlerData> onPresence(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
Future<void> _performQuery(
|
||||
Stanza presence,
|
||||
String ver,
|
||||
String hashFunctionName,
|
||||
String capabilityNode,
|
||||
JID from,
|
||||
) async {
|
||||
if (stanza.from == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
final from = JID.fromString(stanza.from!);
|
||||
final c = stanza.firstTag('c', xmlns: capsXmlns)!;
|
||||
|
||||
final hashFunctionName = c.attributes['hash'] as String?;
|
||||
final capabilityNode = c.attributes['node'] as String?;
|
||||
final ver = c.attributes['ver'] as String?;
|
||||
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Check if we know of the hash
|
||||
final isCached =
|
||||
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
||||
if (isCached) {
|
||||
return state;
|
||||
}
|
||||
|
||||
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
|
||||
final discoRequest = await dm.discoInfoQuery(
|
||||
from,
|
||||
node: capabilityNode,
|
||||
);
|
||||
if (discoRequest.isType<StanzaError>()) {
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
final discoInfo = discoRequest.get<DiscoInfo>();
|
||||
|
||||
@@ -220,7 +201,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
discoInfo,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the disco#info result according to XEP-0115 § 5.4
|
||||
@@ -234,7 +215,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
logger.warning(
|
||||
'Malformed disco#info response: More than one equal identity',
|
||||
);
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +226,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
logger.warning(
|
||||
'Malformed disco#info response: More than one equal feature',
|
||||
);
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +254,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
logger.warning(
|
||||
'Malformed disco#info response: Extended Info FORM_TYPE contains more than one value(s) of different value.',
|
||||
);
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +269,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
logger.warning(
|
||||
'Malformed disco#info response: More than one Extended Disco Info forms with the same FORM_TYPE value',
|
||||
);
|
||||
return state;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the field type is hidden
|
||||
@@ -325,7 +306,43 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
||||
'Capability hash mismatch from $from: Received "$ver", expected "$computedCapabilityHash".',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<StanzaHandlerData> onPresence(
|
||||
Stanza stanza,
|
||||
StanzaHandlerData state,
|
||||
) async {
|
||||
if (stanza.from == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
final from = JID.fromString(stanza.from!);
|
||||
final c = stanza.firstTag('c', xmlns: capsXmlns)!;
|
||||
|
||||
final hashFunctionName = c.attributes['hash'] as String?;
|
||||
final capabilityNode = c.attributes['node'] as String?;
|
||||
final ver = c.attributes['ver'] as String?;
|
||||
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Check if we know of the hash
|
||||
final isCached =
|
||||
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
||||
if (isCached) {
|
||||
return state;
|
||||
}
|
||||
|
||||
unawaited(
|
||||
_performQuery(
|
||||
stanza,
|
||||
ver,
|
||||
hashFunctionName,
|
||||
capabilityNode,
|
||||
from,
|
||||
),
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
|
||||
callback: _onDeliveryRequestReceived,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -27,7 +27,7 @@ class BlockingManager extends XmppManagerBase {
|
||||
tagName: 'block',
|
||||
tagXmlns: blockingXmlns,
|
||||
callback: _blockPush,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -107,10 +107,12 @@ class BlockingManager extends XmppManagerBase {
|
||||
children: items.map((item) {
|
||||
return XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{'jid': item},
|
||||
attributes: {
|
||||
'jid': item,
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -128,7 +130,7 @@ class BlockingManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'unblock',
|
||||
xmlns: blockingXmlns,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -152,11 +154,13 @@ class BlockingManager extends XmppManagerBase {
|
||||
.map(
|
||||
(item) => XMLNode(
|
||||
tag: 'item',
|
||||
attributes: <String, String>{'jid': item},
|
||||
attributes: {
|
||||
'jid': item,
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -174,7 +178,7 @@ class BlockingManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'blocklist',
|
||||
xmlns: blockingXmlns,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,7 +5,10 @@ class StreamManagementEnableNonza extends XMLNode {
|
||||
StreamManagementEnableNonza()
|
||||
: super(
|
||||
tag: 'enable',
|
||||
attributes: <String, String>{'xmlns': smXmlns, 'resume': 'true'},
|
||||
attributes: {
|
||||
'xmlns': smXmlns,
|
||||
'resume': 'true',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,10 +16,10 @@ class StreamManagementResumeNonza extends XMLNode {
|
||||
StreamManagementResumeNonza(String id, int h)
|
||||
: super(
|
||||
tag: 'resume',
|
||||
attributes: <String, String>{
|
||||
attributes: {
|
||||
'xmlns': smXmlns,
|
||||
'previd': id,
|
||||
'h': h.toString()
|
||||
'h': h.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -25,7 +28,10 @@ class StreamManagementAckNonza extends XMLNode {
|
||||
StreamManagementAckNonza(int h)
|
||||
: super(
|
||||
tag: 'a',
|
||||
attributes: <String, String>{'xmlns': smXmlns, 'h': h.toString()},
|
||||
attributes: {
|
||||
'xmlns': smXmlns,
|
||||
'h': h.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +39,7 @@ class StreamManagementRequestNonza extends XMLNode {
|
||||
StreamManagementRequestNonza()
|
||||
: super(
|
||||
tag: 'r',
|
||||
attributes: <String, String>{
|
||||
attributes: {
|
||||
'xmlns': smXmlns,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -75,6 +75,17 @@ class StreamManagementManager extends XmppManagerBase {
|
||||
return acks;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onData() async {
|
||||
// The ack timer does not matter if we are currently in the middle of receiving
|
||||
// data.
|
||||
await _ackLock.synchronized(() {
|
||||
if (_pendingAcks > 0) {
|
||||
_resetAckTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Called when a stanza has been acked to decide whether we should trigger a
|
||||
/// StanzaAckedEvent.
|
||||
///
|
||||
@@ -140,7 +151,7 @@ class StreamManagementManager extends XmppManagerBase {
|
||||
nonzaTag: 'a',
|
||||
nonzaXmlns: smXmlns,
|
||||
callback: _handleAckResponse,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -148,14 +159,14 @@ class StreamManagementManager extends XmppManagerBase {
|
||||
StanzaHandler(
|
||||
callback: _onServerStanzaReceived,
|
||||
priority: 9999,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getOutgoingPostStanzaHandlers() => [
|
||||
StanzaHandler(
|
||||
callback: _onClientStanzaSent,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -225,6 +236,12 @@ class StreamManagementManager extends XmppManagerBase {
|
||||
_ackTimer = null;
|
||||
}
|
||||
|
||||
/// Resets the ack timer.
|
||||
void _resetAckTimer() {
|
||||
_stopAckTimer();
|
||||
_startAckTimer();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
Future<void> handleAckTimeout() async {
|
||||
_stopAckTimer();
|
||||
@@ -315,8 +332,7 @@ class StreamManagementManager extends XmppManagerBase {
|
||||
|
||||
// Reset the timer
|
||||
if (_pendingAcks > 0) {
|
||||
_stopAckTimer();
|
||||
_startAckTimer();
|
||||
_resetAckTimer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class CarbonsManager extends XmppManagerBase {
|
||||
tagXmlns: carbonsXmlns,
|
||||
callback: _onMessageSent,
|
||||
priority: -98,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -124,7 +124,7 @@ class CarbonsManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'enable',
|
||||
xmlns: carbonsXmlns,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -154,7 +154,7 @@ class CarbonsManager extends XmppManagerBase {
|
||||
XMLNode.xmlns(
|
||||
tag: 'disable',
|
||||
xmlns: carbonsXmlns,
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -40,7 +40,7 @@ class LastMessageCorrectionManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -100,7 +100,7 @@ class ChatMarkerManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -81,7 +81,7 @@ class StableIdManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the MessageManager
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -127,7 +127,13 @@ class StableIdManager extends XmppManagerBase {
|
||||
TypedMap<StanzaHandlerExtension> extensions,
|
||||
) {
|
||||
final data = extensions.get<StableIdData>();
|
||||
return data != null ? data.toXML() : [];
|
||||
if (data?.originId != null) {
|
||||
return [
|
||||
data!.toOriginIdElement(),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -161,9 +161,9 @@ class HttpFileUploadManager extends XmppManagerBase {
|
||||
attributes: {
|
||||
'filename': filename,
|
||||
'size': filesize.toString(),
|
||||
...contentType != null ? {'content-type': contentType} : {}
|
||||
if (contentType != null) 'content-type': contentType,
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,10 +2,14 @@ abstract class OmemoError {}
|
||||
|
||||
class UnknownOmemoError extends OmemoError {}
|
||||
|
||||
class InvalidAffixElementsException with Exception {}
|
||||
class InvalidAffixElementsException implements Exception {}
|
||||
|
||||
/// Internal exception that is returned when the device list cannot be
|
||||
/// fetched because the returned list is empty.
|
||||
class EmptyDeviceListException implements OmemoError {}
|
||||
|
||||
class OmemoNotSupportedForContactException extends OmemoError {}
|
||||
|
||||
class EncryptionFailedException with Exception {}
|
||||
class EncryptionFailedException implements Exception {}
|
||||
|
||||
class InvalidEnvelopePayloadException with Exception {}
|
||||
class InvalidEnvelopePayloadException implements Exception {}
|
||||
|
||||
@@ -535,7 +535,10 @@ class OmemoManager extends XmppManagerBase {
|
||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
|
||||
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||
return Result(result.get<List<PubSubItem>>().first.payload);
|
||||
|
||||
final itemList = result.get<List<PubSubItem>>();
|
||||
if (itemList.isEmpty) return Result(EmptyDeviceListException());
|
||||
return Result(itemList.first.payload);
|
||||
}
|
||||
|
||||
/// Retrieves the OMEMO device list from [jid].
|
||||
@@ -580,7 +583,7 @@ class OmemoManager extends XmppManagerBase {
|
||||
int deviceId,
|
||||
) async {
|
||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||
final bareJid = jid.toBare().toString();
|
||||
final bareJid = jid.toBare();
|
||||
final item = await pm.getItem(bareJid, omemoBundlesXmlns, '$deviceId');
|
||||
if (item.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class SIMSManager extends XmppManagerBase {
|
||||
tagXmlns: referenceXmlns,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -31,7 +31,7 @@ class MessageRetractionManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the MessageManager
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -133,7 +133,7 @@ class SFSManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the message handler
|
||||
priority: -98,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
|
||||
@@ -344,7 +344,7 @@ class StickersManager extends XmppManagerBase {
|
||||
) async {
|
||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||
final stickerPackDataRaw = await pm.getItem(
|
||||
jid.toBare().toString(),
|
||||
jid.toBare(),
|
||||
stickersXmlns,
|
||||
id,
|
||||
);
|
||||
|
||||
@@ -96,7 +96,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
||||
callback: _onMessage,
|
||||
// Before the message handler
|
||||
priority: -99,
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -107,8 +107,10 @@ class MessageRepliesManager extends XmppManagerBase {
|
||||
TypedMap<StanzaHandlerExtension> extensions,
|
||||
) {
|
||||
final data = extensions.get<ReplyData>();
|
||||
return data != null
|
||||
? [
|
||||
if (data == null) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
XMLNode.xmlns(
|
||||
tag: 'reply',
|
||||
xmlns: replyXmlns,
|
||||
@@ -127,7 +129,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
||||
if (data.body != null)
|
||||
XMLNode.xmlns(
|
||||
tag: 'fallback',
|
||||
xmlns: fallbackXmlns,
|
||||
xmlns: fallbackIndicationXmlns,
|
||||
attributes: {'for': replyXmlns},
|
||||
children: [
|
||||
XMLNode(
|
||||
@@ -139,8 +141,7 @@ class MessageRepliesManager extends XmppManagerBase {
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
: [];
|
||||
];
|
||||
}
|
||||
|
||||
Future<StanzaHandlerData> _onMessage(
|
||||
@@ -154,7 +155,8 @@ class MessageRepliesManager extends XmppManagerBase {
|
||||
int? end;
|
||||
|
||||
// TODO(Unknown): Maybe extend firstTag to also look for attributes
|
||||
final fallback = stanza.firstTag('fallback', xmlns: fallbackXmlns);
|
||||
final fallback =
|
||||
stanza.firstTag('fallback', xmlns: fallbackIndicationXmlns);
|
||||
if (fallback != null) {
|
||||
final body = fallback.firstTag('body')!;
|
||||
start = int.parse(body.attributes['start']! as String);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#">
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
|
||||
xmlns="http://usefulinc.com/ns/doap#"
|
||||
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||
xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"
|
||||
xmlns:schema="https://schema.org/">
|
||||
<Project xml:lang="en">
|
||||
<name>moxxmpp</name>
|
||||
<created>2021-12-26</created>
|
||||
@@ -9,14 +13,33 @@
|
||||
<os>macOS</os>
|
||||
<os>Android</os>
|
||||
<os>iOS</os>
|
||||
<programming-language>Dart</programming-language>
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Alexander "Polynomdivision"</foaf:name>
|
||||
<foaf:homepage rdf:resource="https://blog.polynom.me"/>
|
||||
<foaf:homepage rdf:resource="https://polynom.me"/>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
|
||||
<!-- Channel list -->
|
||||
<developer-forum rdf:resource='xmpp:dev@muc.moxxy.org?join'/>
|
||||
<support-forum rdf:resource='xmpp:dev@muc.moxxy.org?join'/>
|
||||
|
||||
<!-- Repository information -->
|
||||
<programming-language>Dart</programming-language>
|
||||
<bug-database rdf:resource="https://codeberg.org/moxxy/moxxmpp/issues" />
|
||||
<license rdf:resource="https://codeberg.org/moxxy/moxxmpp/src/branch/master/packages/moxxmpp/LICENSE" />
|
||||
<repository>
|
||||
<GitRepository>
|
||||
<browse rdf:resource="https://codeberg.org/moxxy/moxxmpp" />
|
||||
<location rdf:resource="https://codeberg.org/moxxy/moxxmpp.git" />
|
||||
</GitRepository>
|
||||
</repository>
|
||||
|
||||
<!-- RFC list -->
|
||||
<implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html"/>
|
||||
<implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html"/>
|
||||
|
||||
<!-- Standard XEP list -->
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
|
||||
@@ -57,7 +80,6 @@
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
|
||||
<xmpp:status>partial</xmpp:status>
|
||||
<xmpp:note xml:lang="en">Receiving data</xmpp:note>
|
||||
<xmpp:version>1.1.4</xmpp:version>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
@@ -94,7 +116,6 @@
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:version>1.3.0</xmpp:version>
|
||||
<xmpp:note xml:lang="en">Not plugged into the UI</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
@@ -145,7 +166,6 @@
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
|
||||
<xmpp:status>partial</xmpp:status>
|
||||
<xmpp:note xml:lang="en">Read-only support</xmpp:note>
|
||||
<xmpp:version>0.4</xmpp:version>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
@@ -153,7 +173,6 @@
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0334.html"/>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:note xml:lang="en">Write-only support</xmpp:note>
|
||||
<xmpp:version>0.3.0</xmpp:version>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
@@ -174,9 +193,8 @@
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
|
||||
<xmpp:status>partial</xmpp:status>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:version>1.1.0</xmpp:version>
|
||||
<xmpp:note xml:lang="en">Only handles the success case; not accessible via the App</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
@@ -256,6 +274,16 @@
|
||||
<xmpp:version>0.2.0</xmpp:version>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0484.html" />
|
||||
<xmpp:status>partial</xmpp:status>
|
||||
<xmpp:version>0.1.1</xmpp:version>
|
||||
<xmpp:note xml:lang="en">Invalidation is never requested</xmpp:note>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
|
||||
<!-- Non-Standard (Proto) XEPs -->
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md"/>
|
||||
|
||||
@@ -5,35 +5,28 @@ homepage: https://codeberg.org/moxxy/moxxmpp
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.5 <3.0.0'
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
collection: ^1.16.0
|
||||
cryptography: ^2.0.5
|
||||
collection: ^1.18.0
|
||||
cryptography: ^2.7.0
|
||||
hex: ^0.2.0
|
||||
json_serializable: ^6.3.1
|
||||
logging: ^1.0.2
|
||||
meta: ^1.7.0
|
||||
json_serializable: ^6.8.0
|
||||
logging: ^1.2.0
|
||||
meta: ^1.15.0
|
||||
moxlib:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.2.0
|
||||
omemo_dart:
|
||||
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
|
||||
version: ^0.5.1
|
||||
version: ^0.6.0
|
||||
random_string: ^2.3.1
|
||||
saslprep: ^1.0.2
|
||||
synchronized: ^3.0.0+2
|
||||
uuid: ^3.0.5
|
||||
xml: ^6.1.0
|
||||
saslprep: ^1.0.3
|
||||
synchronized: ^3.3.0+3
|
||||
uuid: ^3.0.7
|
||||
xml: ^6.5.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.11
|
||||
test: ^1.16.0
|
||||
very_good_analysis: ^3.0.1
|
||||
|
||||
# TODO: Remove once we release 0.5.1
|
||||
dependency_overrides:
|
||||
omemo_dart:
|
||||
git:
|
||||
url: https://github.com/PapaTutuWawa/omemo_dart.git
|
||||
rev: 49c7e114e6cf80dcde55fbbd218bba3182045862
|
||||
build_runner: ^2.4.12
|
||||
test: ^1.25.8
|
||||
very_good_analysis: ^6.0.0
|
||||
|
||||
@@ -2,11 +2,12 @@ import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp/src/awaiter.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
const bareJid = JID('moxxmpp', 'server3.example', '');
|
||||
const bareJid = 'user4@example.org';
|
||||
String getBareJidCallback() => bareJid;
|
||||
|
||||
void main() {
|
||||
test('Test awaiting an awaited stanza with a from attribute', () async {
|
||||
final awaiter = StanzaAwaiter();
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(
|
||||
@@ -20,14 +21,12 @@ void main() {
|
||||
XMLNode.fromString(
|
||||
'<iq from="user3@server.example" id="abc123" type="result" />',
|
||||
),
|
||||
bareJid,
|
||||
);
|
||||
expect(result1, false);
|
||||
final result2 = await awaiter.onData(
|
||||
XMLNode.fromString(
|
||||
'<iq from="user1@server.example" id="lol" type="result" />',
|
||||
),
|
||||
bareJid,
|
||||
);
|
||||
expect(result2, false);
|
||||
|
||||
@@ -37,22 +36,20 @@ void main() {
|
||||
);
|
||||
final result3 = await awaiter.onData(
|
||||
stanza,
|
||||
bareJid,
|
||||
);
|
||||
expect(result3, true);
|
||||
expect(await future, stanza);
|
||||
});
|
||||
|
||||
test('Test awaiting an awaited stanza without a from attribute', () async {
|
||||
final awaiter = StanzaAwaiter();
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
||||
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||
|
||||
// Receive the wrong answer
|
||||
final result1 = await awaiter.onData(
|
||||
XMLNode.fromString('<iq id="lol" type="result" />'),
|
||||
bareJid,
|
||||
);
|
||||
expect(result1, false);
|
||||
|
||||
@@ -60,23 +57,21 @@ void main() {
|
||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||
final result2 = await awaiter.onData(
|
||||
stanza,
|
||||
bareJid,
|
||||
);
|
||||
expect(result2, true);
|
||||
expect(await future, stanza);
|
||||
});
|
||||
|
||||
test('Test awaiting a stanza that was already awaited', () async {
|
||||
final awaiter = StanzaAwaiter();
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
||||
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||
|
||||
// Receive the correct answer
|
||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||
final result1 = await awaiter.onData(
|
||||
stanza,
|
||||
bareJid,
|
||||
);
|
||||
expect(result1, true);
|
||||
expect(await future, stanza);
|
||||
@@ -84,31 +79,55 @@ void main() {
|
||||
// Receive it again
|
||||
final result2 = await awaiter.onData(
|
||||
stanza,
|
||||
bareJid,
|
||||
);
|
||||
expect(result2, false);
|
||||
});
|
||||
|
||||
test('Test ignoring a stanza that has the wrong tag', () async {
|
||||
final awaiter = StanzaAwaiter();
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(bareJid.toString(), 'abc123', 'iq');
|
||||
final future = await awaiter.addPending(null, 'abc123', 'iq');
|
||||
|
||||
// Receive the wrong answer
|
||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||
final result1 = await awaiter.onData(
|
||||
XMLNode.fromString('<message id="abc123" type="result" />'),
|
||||
bareJid,
|
||||
);
|
||||
expect(result1, false);
|
||||
|
||||
// Receive the correct answer
|
||||
final result2 = await awaiter.onData(
|
||||
stanza,
|
||||
bareJid,
|
||||
);
|
||||
expect(result2, true);
|
||||
expect(await future, stanza);
|
||||
});
|
||||
|
||||
test('Sending a stanza to our bare JID', () async {
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(bareJid, 'abc123', 'iq');
|
||||
|
||||
// Receive the response.
|
||||
final stanza = XMLNode.fromString('<iq id="abc123" type="result" />');
|
||||
await awaiter.onData(stanza);
|
||||
expect(await future, stanza);
|
||||
});
|
||||
|
||||
test(
|
||||
'Sending a stanza to our bare JID and receiving stanza with a from attribute',
|
||||
() async {
|
||||
final awaiter = StanzaAwaiter(getBareJidCallback);
|
||||
|
||||
// "Send" a stanza
|
||||
final future = await awaiter.addPending(bareJid, 'abc123', 'iq');
|
||||
|
||||
// Receive the response.
|
||||
final stanza =
|
||||
XMLNode.fromString('<iq from="$bareJid" id="abc123" type="result" />');
|
||||
await awaiter.onData(stanza);
|
||||
expect(await future, stanza);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ List<ExpectationBase> buildAuthenticatedPlay(ConnectionSettings settings) {
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<ver xmlns='urn:xmpp:features:rosterver'/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
|
||||
53
packages/moxxmpp/test/roster_test.dart
Normal file
53
packages/moxxmpp/test/roster_test.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'helpers/logging.dart';
|
||||
import 'helpers/xmpp.dart';
|
||||
|
||||
void main() {
|
||||
initLogger();
|
||||
|
||||
test('Test a versioned roster fetch returning an empty iq', () async {
|
||||
final sm = TestingRosterStateManager('ver14', []);
|
||||
final rm = RosterManager(sm);
|
||||
final cs = ConnectionSettings(
|
||||
jid: JID.fromString('user@example.org'),
|
||||
password: 'abc123',
|
||||
);
|
||||
final socket = StubTCPSocket([
|
||||
...buildAuthenticatedPlay(cs),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" id="r1h3vzp7" type="get"><query xmlns="jabber:iq:roster" ver="ver14"/></iq>',
|
||||
'<iq xmlns="jabber:client" id="r1h3vzp7" type="result" />',
|
||||
ignoreId: true,
|
||||
adjustId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
socket,
|
||||
)..connectionSettings = cs;
|
||||
await conn.registerManagers([
|
||||
rm,
|
||||
PresenceManager(),
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
RosterFeatureNegotiator(),
|
||||
]);
|
||||
|
||||
// Connect
|
||||
await conn.connect(
|
||||
shouldReconnect: false,
|
||||
waitUntilLogin: true,
|
||||
);
|
||||
|
||||
// Request the roster
|
||||
final rawResult = await rm.requestRoster();
|
||||
expect(rawResult.isType<RosterRequestResult>(), true);
|
||||
final result = rawResult.get<RosterRequestResult>();
|
||||
expect(result.items.isEmpty, true);
|
||||
});
|
||||
}
|
||||
@@ -14,9 +14,9 @@ final scramSha1StreamFeatures = XMLNode(
|
||||
XMLNode(
|
||||
tag: 'mechanism',
|
||||
text: 'SCRAM-SHA-1',
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
final scramSha256StreamFeatures = XMLNode(
|
||||
@@ -29,9 +29,9 @@ final scramSha256StreamFeatures = XMLNode(
|
||||
XMLNode(
|
||||
tag: 'mechanism',
|
||||
text: 'SCRAM-SHA-256',
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ void main() {
|
||||
),
|
||||
tagName: '3',
|
||||
priority: 50,
|
||||
)
|
||||
),
|
||||
]..sort(stanzaHandlerSortComparator);
|
||||
|
||||
expect(handlerList[0].tagName, '1');
|
||||
|
||||
874
packages/moxxmpp/test/xeps/xep_0045_test.dart
Normal file
874
packages/moxxmpp/test/xeps/xep_0045_test.dart
Normal file
@@ -0,0 +1,874 @@
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import '../helpers/logging.dart';
|
||||
import '../helpers/xmpp.dart';
|
||||
|
||||
void main() {
|
||||
initLogger();
|
||||
|
||||
test(
|
||||
'Test connecting to MUCs after a reconnection without stream resumption',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client"><subject/></message>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client"><subject/></message>',
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingSleepReconnectionPolicy(1),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)
|
||||
..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
)
|
||||
..setResource('test-resource', triggerEvent: false);
|
||||
await conn.registerManagers([
|
||||
DiscoManager([]),
|
||||
MUCManager(),
|
||||
]);
|
||||
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
]);
|
||||
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: true,
|
||||
);
|
||||
|
||||
// Join a groupchat
|
||||
final joinResult =
|
||||
await conn.getManagerById<MUCManager>(mucManager)!.joinRoom(
|
||||
JID.fromString('channel@muc.example.org'),
|
||||
'test',
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
expect(joinResult.isType<bool>(), true);
|
||||
expect(joinResult.get<bool>(), true);
|
||||
|
||||
// Trigger a reconnection reason.
|
||||
Logger('Test').info('Injecting socket fault');
|
||||
fakeSocket.injectSocketFault();
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 4));
|
||||
expect(fakeSocket.getState(), 10);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Test joining a MUC with other members',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'',
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingSleepReconnectionPolicy(1),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)
|
||||
..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
)
|
||||
..setResource('test-resource', triggerEvent: false);
|
||||
await conn.registerManagers([
|
||||
DiscoManager([]),
|
||||
MUCManager(),
|
||||
]);
|
||||
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
]);
|
||||
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
);
|
||||
|
||||
// Join a groupchat
|
||||
final roomJid = JID.fromString('channel@muc.example.org');
|
||||
final joinResult = conn.getManagerById<MUCManager>(mucManager)!.joinRoom(
|
||||
roomJid,
|
||||
'test',
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
fakeSocket
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/firstwitch'
|
||||
id='3DCB0401-D7CF-4E31-BE05-EDF8D057BFBD'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='owner' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/secondwitch'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23D'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/test'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23E'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='member' role='none'/>
|
||||
<status code='110' />
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client">
|
||||
<subject/>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await joinResult;
|
||||
expect(fakeSocket.getState(), 5);
|
||||
|
||||
final room = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(room.joined, true);
|
||||
expect(
|
||||
room.members.length,
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
room.members['test'],
|
||||
null,
|
||||
);
|
||||
expect(
|
||||
room.members['secondwitch']!.role,
|
||||
Role.moderator,
|
||||
);
|
||||
expect(
|
||||
room.members['secondwitch']!.affiliation,
|
||||
Affiliation.admin,
|
||||
);
|
||||
expect(
|
||||
room.members['firstwitch']!.role,
|
||||
Role.moderator,
|
||||
);
|
||||
expect(
|
||||
room.members['firstwitch']!.affiliation,
|
||||
Affiliation.owner,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Testing a user joining a room',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'',
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingSleepReconnectionPolicy(1),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)
|
||||
..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
)
|
||||
..setResource('test-resource', triggerEvent: false);
|
||||
await conn.registerManagers([
|
||||
DiscoManager([]),
|
||||
MUCManager(),
|
||||
]);
|
||||
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
]);
|
||||
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
);
|
||||
|
||||
// Join a groupchat
|
||||
final roomJid = JID.fromString('channel@muc.example.org');
|
||||
final joinResult = conn.getManagerById<MUCManager>(mucManager)!.joinRoom(
|
||||
roomJid,
|
||||
'test',
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
fakeSocket
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/firstwitch'
|
||||
id='3DCB0401-D7CF-4E31-BE05-EDF8D057BFBD'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='owner' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/secondwitch'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23D'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/test'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23E'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='member' role='none'/>
|
||||
<status code='110' />
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client">
|
||||
<subject/>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await joinResult;
|
||||
final room = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(room.joined, true);
|
||||
expect(
|
||||
room.members.length,
|
||||
2,
|
||||
);
|
||||
|
||||
// Now a new user joins the room.
|
||||
MemberJoinedEvent? event;
|
||||
conn.asBroadcastStream().listen((e) {
|
||||
if (e is MemberJoinedEvent) {
|
||||
event = e;
|
||||
}
|
||||
});
|
||||
|
||||
fakeSocket.injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/papatutuwawa'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23G'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='participant'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
expect(event != null, true);
|
||||
expect(event!.member.nick, 'papatutuwawa');
|
||||
expect(event!.member.affiliation, Affiliation.admin);
|
||||
expect(event!.member.role, Role.participant);
|
||||
|
||||
final roomAfterJoin = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(roomAfterJoin.members.length, 3);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Testing a user leaving a room',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'',
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingSleepReconnectionPolicy(1),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)
|
||||
..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
)
|
||||
..setResource('test-resource', triggerEvent: false);
|
||||
await conn.registerManagers([
|
||||
DiscoManager([]),
|
||||
MUCManager(),
|
||||
]);
|
||||
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
]);
|
||||
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
);
|
||||
|
||||
// Join a groupchat
|
||||
final roomJid = JID.fromString('channel@muc.example.org');
|
||||
final joinResult = conn.getManagerById<MUCManager>(mucManager)!.joinRoom(
|
||||
roomJid,
|
||||
'test',
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
fakeSocket
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/firstwitch'
|
||||
id='3DCB0401-D7CF-4E31-BE05-EDF8D057BFBD'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='owner' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/secondwitch'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23D'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/test'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23E'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='member' role='none'/>
|
||||
<status code='110' />
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client">
|
||||
<subject/>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await joinResult;
|
||||
final room = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(room.joined, true);
|
||||
expect(
|
||||
room.members.length,
|
||||
2,
|
||||
);
|
||||
|
||||
// Now a user leaves the room.
|
||||
MemberLeftEvent? event;
|
||||
conn.asBroadcastStream().listen((e) {
|
||||
if (e is MemberLeftEvent) {
|
||||
event = e;
|
||||
}
|
||||
});
|
||||
|
||||
fakeSocket.injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/secondwitch'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23G'
|
||||
type='unavailable'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='none'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
expect(event != null, true);
|
||||
expect(event!.nick, 'secondwitch');
|
||||
|
||||
final roomAfterLeave = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(roomAfterLeave.members.length, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'Test a user changing their nick name',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<presence to="channel@muc.example.org/test" xmlns="jabber:client"><x xmlns="http://jabber.org/protocol/muc"><history maxstanzas="0"/></x></presence>',
|
||||
'',
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingSleepReconnectionPolicy(1),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)
|
||||
..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('polynomdivision@test.server'),
|
||||
password: 'aaaa',
|
||||
)
|
||||
..setResource('test-resource', triggerEvent: false);
|
||||
await conn.registerManagers([
|
||||
DiscoManager([]),
|
||||
MUCManager(),
|
||||
]);
|
||||
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
]);
|
||||
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
shouldReconnect: false,
|
||||
);
|
||||
|
||||
// Join a groupchat
|
||||
final roomJid = JID.fromString('channel@muc.example.org');
|
||||
final joinResult = conn.getManagerById<MUCManager>(mucManager)!.joinRoom(
|
||||
roomJid,
|
||||
'test',
|
||||
maxHistoryStanzas: 0,
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
fakeSocket
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/firstwitch'
|
||||
id='3DCB0401-D7CF-4E31-BE05-EDF8D057BFBD'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='owner' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/secondwitch'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23D'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='admin' role='moderator'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/test'
|
||||
id='C2CD9EE3-8421-431E-854A-A2AD0CE2E23E'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='member' role='none'/>
|
||||
<status code='110' />
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
)
|
||||
..injectRawXml(
|
||||
'''
|
||||
<message from="channel@muc.example.org" type="groupchat" xmlns="jabber:client">
|
||||
<subject/>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await joinResult;
|
||||
final room = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(room.joined, true);
|
||||
expect(
|
||||
room.members.length,
|
||||
2,
|
||||
);
|
||||
|
||||
// Now a new user changes their nick.
|
||||
MemberChangedNickEvent? event;
|
||||
conn.asBroadcastStream().listen((e) {
|
||||
if (e is MemberChangedNickEvent) {
|
||||
event = e;
|
||||
}
|
||||
});
|
||||
|
||||
fakeSocket.injectRawXml(
|
||||
'''
|
||||
<presence
|
||||
from='channel@muc.example.org/firstwitch'
|
||||
id='3DCB0401-D7CF-4E31-BE05-EDF8D057BFBD'
|
||||
type='unavailable'>
|
||||
<x xmlns='http://jabber.org/protocol/muc#user'>
|
||||
<item affiliation='owner' role='moderator' nick='papatutuwawa'/>
|
||||
<status code='303'/>
|
||||
</x>
|
||||
</presence>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
expect(event != null, true);
|
||||
expect(event!.oldNick, 'firstwitch');
|
||||
expect(event!.newNick, 'papatutuwawa');
|
||||
|
||||
final roomAfterChange = (await conn
|
||||
.getManagerById<MUCManager>(mucManager)!
|
||||
.getRoomState(roomJid))!;
|
||||
expect(roomAfterChange.members.length, 2);
|
||||
expect(roomAfterChange.members['firstwitch'], null);
|
||||
expect(roomAfterChange.members['papatutuwawa'] != null, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -158,7 +158,7 @@ void main() {
|
||||
</iq>''',
|
||||
ignoreId: true,
|
||||
adjustId: true,
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class StubbedDiscoManager extends DiscoManager {
|
||||
isRequired: false,
|
||||
varAttr: 'FORM_TYPE',
|
||||
type: 'hidden',
|
||||
)
|
||||
),
|
||||
],
|
||||
reported: [],
|
||||
items: [],
|
||||
@@ -153,14 +153,14 @@ void main() {
|
||||
'http://jabber.org/protocol/caps',
|
||||
'http://jabber.org/protocol/disco#info',
|
||||
'http://jabber.org/protocol/disco#items',
|
||||
'http://jabber.org/protocol/muc'
|
||||
'http://jabber.org/protocol/muc',
|
||||
],
|
||||
const [
|
||||
Identity(
|
||||
category: 'client',
|
||||
type: 'pc',
|
||||
name: 'Exodus 0.9.1',
|
||||
)
|
||||
),
|
||||
],
|
||||
const [],
|
||||
null,
|
||||
@@ -179,7 +179,7 @@ void main() {
|
||||
'http://jabber.org/protocol/caps',
|
||||
'http://jabber.org/protocol/disco#info',
|
||||
'http://jabber.org/protocol/disco#items',
|
||||
'http://jabber.org/protocol/muc'
|
||||
'http://jabber.org/protocol/muc',
|
||||
],
|
||||
const [
|
||||
Identity(
|
||||
@@ -295,14 +295,14 @@ void main() {
|
||||
'urn:xmpp:message-correct:0',
|
||||
'urn:xmpp:ping',
|
||||
'urn:xmpp:receipts',
|
||||
'urn:xmpp:time'
|
||||
'urn:xmpp:time',
|
||||
],
|
||||
const [
|
||||
Identity(
|
||||
category: 'client',
|
||||
type: 'phone',
|
||||
name: 'Conversations',
|
||||
)
|
||||
),
|
||||
],
|
||||
const [],
|
||||
null,
|
||||
@@ -343,6 +343,7 @@ void main() {
|
||||
stanza,
|
||||
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
|
||||
expect(
|
||||
await manager.getCachedDiscoInfoFromJid(aliceJid) != null,
|
||||
@@ -513,6 +514,7 @@ void main() {
|
||||
stanza,
|
||||
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
|
||||
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
||||
expect(
|
||||
@@ -549,6 +551,7 @@ void main() {
|
||||
stanza,
|
||||
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||
);
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
|
||||
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
||||
expect(
|
||||
|
||||
@@ -433,7 +433,8 @@ void main() {
|
||||
});
|
||||
|
||||
test('Test a failed stream resumption', () async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
final fakeSocket = StubTCPSocket(
|
||||
[
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
@@ -486,8 +487,9 @@ void main() {
|
||||
StringExpectation(
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="id-2" resume="true" />',
|
||||
)
|
||||
]);
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
|
||||
@@ -115,7 +115,8 @@ void main() {
|
||||
test('Test sending a message processing hint', () async {
|
||||
final manager = MessageManager();
|
||||
final holder = TestingManagerHolder(
|
||||
stubSocket: StubTCPSocket([
|
||||
stubSocket: StubTCPSocket(
|
||||
[
|
||||
StanzaExpectation(
|
||||
'''
|
||||
<message to="user@example.org" type="chat">
|
||||
@@ -124,8 +125,9 @@ void main() {
|
||||
</message>
|
||||
''',
|
||||
'',
|
||||
)
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
await holder.register([
|
||||
|
||||
@@ -6,7 +6,7 @@ void main() {
|
||||
test('invariance', () {
|
||||
final headers = {
|
||||
'authorization': 'Basic Base64String==',
|
||||
'cookie': 'foo=bar; user=romeo'
|
||||
'cookie': 'foo=bar; user=romeo',
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
@@ -16,7 +16,7 @@ void main() {
|
||||
test('invariance through uppercase', () {
|
||||
final headers = {
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo'
|
||||
'Cookie': 'foo=bar; user=romeo',
|
||||
};
|
||||
expect(
|
||||
prepareHeaders(headers),
|
||||
@@ -27,7 +27,7 @@ void main() {
|
||||
final headers = {
|
||||
'Authorization': 'Basic Base64String==',
|
||||
'Cookie': 'foo=bar; user=romeo',
|
||||
'X-Tracking': 'Base64String=='
|
||||
'X-Tracking': 'Base64String==',
|
||||
};
|
||||
expect(prepareHeaders(headers), {
|
||||
'Authorization': 'Basic Base64String==',
|
||||
|
||||
@@ -216,6 +216,89 @@ void main() {
|
||||
expect(result.isType<XmppError>(), false);
|
||||
});
|
||||
|
||||
test('Test SCRAM-SHA-1 SASL2 negotiation with a valid signature', () async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='server' from='user@server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
<mechanism>SCRAM-SHA-1</mechanism>
|
||||
</mechanisms>
|
||||
<authentication xmlns='urn:xmpp:sasl:2'>
|
||||
<mechanism>PLAIN</mechanism>
|
||||
<mechanism>SCRAM-SHA-1</mechanism>
|
||||
</authentication>
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
</stream:features>''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='SCRAM-SHA-1'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM</initial-response></authenticate>",
|
||||
'''
|
||||
<challenge xmlns='urn:xmpp:sasl:2'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<response xmlns="urn:xmpp:sasl:2">Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMM3JmY05IWUpZMVpWdldWczdqLHA9djBYOHYzQnoyVDBDSkdiSlF5RjBYK0hJNFRzPQ==</response>',
|
||||
'<success xmlns="urn:xmpp:sasl:2"><additional-data>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</additional-data><authorization-identifier>user@server</authorization-identifier></success>',
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<iq xmlns='jabber:client' type='set' id='aaaa'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' /></iq>",
|
||||
'''
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
''',
|
||||
adjustId: true,
|
||||
ignoreId: true,
|
||||
),
|
||||
]);
|
||||
final conn = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
fakeSocket,
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('user@server'),
|
||||
password: 'pencil',
|
||||
);
|
||||
await conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(TestingRosterStateManager('', [])),
|
||||
DiscoManager([]),
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
SaslScramNegotiator(
|
||||
10,
|
||||
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
|
||||
'fyko+d2lbbFgONRv9qkxdawL',
|
||||
ScramHashType.sha1,
|
||||
),
|
||||
ResourceBindingNegotiator(),
|
||||
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<NegotiatorError>(), false);
|
||||
});
|
||||
|
||||
test('Test SCRAM-SHA-1 SASL2 negotiation with an invalid signature',
|
||||
() async {
|
||||
final fakeSocket = StubTCPSocket([
|
||||
|
||||
@@ -253,7 +253,7 @@ void main() {
|
||||
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||
<body>> Anna wrote:\n> We should bake a cake\nGreat idea!</body>
|
||||
<reply to='anna@example.com/laptop' id='message-id1' xmlns='urn:xmpp:reply:0' />
|
||||
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
|
||||
<fallback xmlns='urn:xmpp:fallback:0' for='urn:xmpp:reply:0'>
|
||||
<body start="0" end="38" />
|
||||
</fallback>
|
||||
</message>
|
||||
|
||||
@@ -11,8 +11,9 @@ void main() {
|
||||
final controller = StreamController<String>();
|
||||
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach((event) {
|
||||
if (event is! XMPPStreamElement) return;
|
||||
controller.stream.transform(parser).forEach((events) {
|
||||
for (final event in events) {
|
||||
if (event is! XMPPStreamElement) continue;
|
||||
final node = event.node;
|
||||
|
||||
if (node.tag == 'childa') {
|
||||
@@ -20,6 +21,7 @@ void main() {
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
controller.add('<childa /><childb />');
|
||||
@@ -36,8 +38,9 @@ void main() {
|
||||
final controller = StreamController<String>();
|
||||
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach((event) {
|
||||
if (event is! XMPPStreamElement) return;
|
||||
controller.stream.transform(parser).forEach((events) {
|
||||
for (final event in events) {
|
||||
if (event is! XMPPStreamElement) continue;
|
||||
final node = event.node;
|
||||
|
||||
if (node.tag == 'childa') {
|
||||
@@ -45,6 +48,7 @@ void main() {
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
controller
|
||||
@@ -64,8 +68,9 @@ void main() {
|
||||
final controller = StreamController<String>();
|
||||
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach((event) {
|
||||
if (event is! XMPPStreamElement) return;
|
||||
controller.stream.transform(parser).forEach((events) {
|
||||
for (final event in events) {
|
||||
if (event is! XMPPStreamElement) continue;
|
||||
final node = event.node;
|
||||
|
||||
if (node.tag == 'childa') {
|
||||
@@ -73,6 +78,7 @@ void main() {
|
||||
} else if (node.tag == 'childb') {
|
||||
childb = true;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
controller
|
||||
@@ -93,13 +99,15 @@ void main() {
|
||||
final controller = StreamController<String>();
|
||||
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach((node) {
|
||||
if (node is XMPPStreamElement) {
|
||||
if (node.node.tag == 'childa') {
|
||||
controller.stream.transform(parser).forEach((events) {
|
||||
for (final event in events) {
|
||||
if (event is XMPPStreamElement) {
|
||||
if (event.node.tag == 'childa') {
|
||||
childa = true;
|
||||
}
|
||||
} else if (node is XMPPStreamHeader) {
|
||||
attrs = node.attributes;
|
||||
} else if (event is XMPPStreamHeader) {
|
||||
attrs = event.attributes;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -118,12 +126,14 @@ void main() {
|
||||
var gotFeatures = false;
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach(
|
||||
(event) {
|
||||
if (event is! XMPPStreamElement) return;
|
||||
(events) {
|
||||
for (final event in events) {
|
||||
if (event is! XMPPStreamElement) continue;
|
||||
|
||||
if (event.node.tag == 'stream:features') {
|
||||
gotFeatures = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -157,4 +167,27 @@ void main() {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
expect(gotFeatures, true);
|
||||
});
|
||||
|
||||
test('Test the order of concatenated stanzas', () async {
|
||||
// NOTE: This seems weird, but it turns out that not keeping this order leads to
|
||||
// MUC joins (on Moxxy) not catching every bit of presence before marking the
|
||||
// MUC as joined.
|
||||
final parser = XMPPStreamParser();
|
||||
final controller = StreamController<String>();
|
||||
var called = false;
|
||||
|
||||
unawaited(
|
||||
controller.stream.transform(parser).forEach((events) {
|
||||
expect(events.isNotEmpty, true);
|
||||
expect((events[0] as XMPPStreamElement).node.tag, 'childa');
|
||||
expect((events[1] as XMPPStreamElement).node.tag, 'childb');
|
||||
expect((events[2] as XMPPStreamElement).node.tag, 'childc');
|
||||
called = true;
|
||||
}),
|
||||
);
|
||||
controller.add('<childa /><childb /><childc />');
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
expect(called, true);
|
||||
});
|
||||
}
|
||||
|
||||
30
packages/moxxmpp_color/.gitignore
vendored
Normal file
30
packages/moxxmpp_color/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
10
packages/moxxmpp_color/.metadata
Normal file
10
packages/moxxmpp_color/.metadata
Normal file
@@ -0,0 +1,10 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "efbf63d9c66b9f6ec30e9ad4611189aa80003d31"
|
||||
channel: "stable"
|
||||
|
||||
project_type: package
|
||||
3
packages/moxxmpp_color/CHANGELOG.md
Normal file
3
packages/moxxmpp_color/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.1.0
|
||||
|
||||
- Implement functions to compute a color from a given input, following XEP-0392.
|
||||
22
packages/moxxmpp_color/LICENSE
Normal file
22
packages/moxxmpp_color/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Alexander "PapaTutuWawa"
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
7
packages/moxxmpp_color/README.md
Normal file
7
packages/moxxmpp_color/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# moxxmpp_color
|
||||
|
||||
An implementation of [XEP-0392](https://xmpp.org/extensions/xep-0392.html).
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE`.
|
||||
10
packages/moxxmpp_color/analysis_options.yaml
Normal file
10
packages/moxxmpp_color/analysis_options.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
include: package:very_good_analysis/analysis_options.yaml
|
||||
linter:
|
||||
rules:
|
||||
public_member_api_docs: false
|
||||
lines_longer_than_80_chars: false
|
||||
use_setters_to_change_properties: false
|
||||
avoid_positional_boolean_parameters: false
|
||||
avoid_bool_literals_in_conditional_expressions: false
|
||||
file_names: false
|
||||
unnecessary_library_directive: false
|
||||
56
packages/moxxmpp_color/lib/moxxmpp_color.dart
Normal file
56
packages/moxxmpp_color/lib/moxxmpp_color.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
library moxxmpp_color;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hsluv/extensions.dart';
|
||||
|
||||
/// The default saturation to use.
|
||||
const _defaultSaturation = 50;
|
||||
|
||||
/// The default lightness to use.
|
||||
const _defaultLightness = 50;
|
||||
|
||||
/// Implementation of the algorithm in XEP-0392. [hashBytes] are the bytes
|
||||
/// of the SHA-1 hash of the input.
|
||||
Color _computeColor(
|
||||
List<int> hashBytes, {
|
||||
double? saturation,
|
||||
double? lightness,
|
||||
}) {
|
||||
final bytes = hashBytes.sublist(0, 2);
|
||||
final angle = (bytes.last << 8 + bytes.first).toDouble() / 65565;
|
||||
return hsluvToRGBColor([
|
||||
angle * 360,
|
||||
(saturation ?? _defaultSaturation).remainder(360),
|
||||
(lightness ?? _defaultLightness).remainder(360),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Like [consistentColor], but synchronous.
|
||||
Color consistentColorSync(
|
||||
String input, {
|
||||
double? saturation,
|
||||
double? lightness,
|
||||
}) {
|
||||
return _computeColor(
|
||||
Sha1().toSync().hashSync(utf8.encode(input)).bytes,
|
||||
saturation: saturation,
|
||||
lightness: lightness,
|
||||
);
|
||||
}
|
||||
|
||||
/// Compute the color based on the algorithm described in XEP-0392.
|
||||
/// [saturation] and [lightness] can be used to supply values to use
|
||||
/// instead of the default.
|
||||
Future<Color> consistentColor(
|
||||
String input, {
|
||||
double? saturation,
|
||||
double? lightness,
|
||||
}) async {
|
||||
return _computeColor(
|
||||
(await Sha1().hash(utf8.encode(input))).bytes,
|
||||
saturation: saturation,
|
||||
lightness: lightness,
|
||||
);
|
||||
}
|
||||
22
packages/moxxmpp_color/pubspec.yaml
Normal file
22
packages/moxxmpp_color/pubspec.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: moxxmpp_color
|
||||
description: Implementation of XEP-0392
|
||||
version: 0.1.0
|
||||
homepage: https://codeberg.org/moxxy/moxxmpp
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
cryptography: ^2.7.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
hsluv: ^1.1.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
very_good_analysis: ^5.1.0
|
||||
|
||||
flutter:
|
||||
@@ -1,3 +1,8 @@
|
||||
## 0.4.0
|
||||
|
||||
- Keep version in sync with moxxmpp
|
||||
- *BREAKING*: `TCPSocketWrapper` now takes a boolean parameter that enables logging of all incoming and outgoing data.
|
||||
|
||||
## 0.3.1
|
||||
|
||||
- Keep version in sync with moxxmpp
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:test/test.dart';
|
||||
|
||||
Future<void> _runTest(String domain) async {
|
||||
var gotTLSException = false;
|
||||
final socket = TCPSocketWrapper();
|
||||
final socket = TCPSocketWrapper(true);
|
||||
final log = Logger('TestLogger');
|
||||
socket.getEventStream().listen((event) {
|
||||
if (event is XmppSocketTLSFailedEvent) {
|
||||
|
||||
@@ -19,7 +19,7 @@ void main() {
|
||||
TestingSleepReconnectionPolicy(10),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
TCPSocketWrapper(),
|
||||
TCPSocketWrapper(true),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
||||
password: 'abc123',
|
||||
@@ -59,7 +59,7 @@ void main() {
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
TCPSocketWrapper(),
|
||||
TCPSocketWrapper(true),
|
||||
)..connectionSettings = ConnectionSettings(
|
||||
jid: JID.fromString('testuser@no-sasl.badxmpp.eu'),
|
||||
password: 'abc123',
|
||||
|
||||
@@ -10,6 +10,11 @@ import 'package:moxxmpp_socket_tcp/src/rfc_2782.dart';
|
||||
|
||||
/// TCP socket implementation for XmppConnection
|
||||
class TCPSocketWrapper extends BaseSocketWrapper {
|
||||
TCPSocketWrapper(this._logIncomingOutgoing);
|
||||
|
||||
/// Flag controlling whether incoming/outgoing data is logged or not.
|
||||
final bool _logIncomingOutgoing;
|
||||
|
||||
/// The underlying Socket/SecureSocket instance.
|
||||
Socket? _socket;
|
||||
|
||||
@@ -212,7 +217,9 @@ class TCPSocketWrapper extends BaseSocketWrapper {
|
||||
_socketSubscription = _socket!.listen(
|
||||
(List<int> event) {
|
||||
final data = utf8.decode(event);
|
||||
if (_logIncomingOutgoing) {
|
||||
_log.finest('<== $data');
|
||||
}
|
||||
_dataStream.add(data);
|
||||
},
|
||||
onError: (Object error) {
|
||||
@@ -297,7 +304,9 @@ class TCPSocketWrapper extends BaseSocketWrapper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_logIncomingOutgoing) {
|
||||
_log.finest('==> $data');
|
||||
}
|
||||
|
||||
try {
|
||||
_socket!.write(data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: moxxmpp_socket_tcp
|
||||
description: A socket for moxxmpp using TCP that implements the RFC6120 connection algorithm and XEP-0368
|
||||
version: 0.3.1
|
||||
version: 0.4.0
|
||||
homepage: https://codeberg.org/moxxy/moxxmpp
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
@@ -14,12 +14,6 @@ dependencies:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: ^0.4.0
|
||||
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
git:
|
||||
url: https://codeberg.org/moxxy/moxxmpp.git
|
||||
rev: 05e3d804a4036e9cd93fd27473a1e970fda3c3fc
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# melos_managed_dependency_overrides: moxxmpp
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../moxxmpp
|
||||
Reference in New Issue
Block a user