Merge branch 'nico/lints' into 'main'

feat: switch from pedantic to lints

See merge request famedly/company/frontend/famedlysdk!1094
This commit is contained in:
Nicolas Werner 2022-08-15 14:15:51 +00:00
commit a47833ab80
61 changed files with 367 additions and 365 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.pyc *.pyc
*.swp *.swp
*.swo *.swo
*.swn
*.dylib *.dylib
.DS_Store .DS_Store
.atom/ .atom/

View File

@ -1,12 +1,21 @@
include: package:pedantic/analysis_options.yaml include: package:lints/recommended.yaml
linter: linter:
rules: rules:
- camel_case_types camel_case_types: true
- avoid_print avoid_print: true
- constant_identifier_names constant_identifier_names: true
- prefer_final_locals prefer_final_locals: true
- prefer_final_in_for_each prefer_final_in_for_each: true
sort_pub_dependencies: true
always_use_package_imports: true
always_declare_return_types: true
prefer_single_quotes: true
sort_child_properties_last: true
unawaited_futures: true
unsafe_html: true
avoid_function_literals_in_foreach_calls: false
non_constant_identifier_names: false # seems to wrongly diagnose static const variables
analyzer: analyzer:
errors: errors:

View File

@ -20,10 +20,10 @@ import 'dart:typed_data';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/ssss.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import '../matrix.dart'; import 'package:matrix/matrix.dart';
import 'encryption.dart';
import 'ssss.dart';
class CrossSigning { class CrossSigning {
final Encryption encryption; final Encryption encryption;
@ -131,14 +131,14 @@ class CrossSigning {
throw Exception('[sign] keys are not in cache but sign was called'); throw Exception('[sign] keys are not in cache but sign was called');
} }
final addSignature = void addSignature(
(SignableKey key, SignableKey signedWith, String signature) { SignableKey key, SignableKey signedWith, String signature) {
final signedKey = key.cloneForSigning(); final signedKey = key.cloneForSigning();
((signedKey.signatures ??= ((signedKey.signatures ??=
<String, Map<String, String>>{})[signedWith.userId] ??= <String, Map<String, String>>{})[signedWith.userId] ??=
<String, String>{})['ed25519:${signedWith.identifier}'] = signature; <String, String>{})['ed25519:${signedWith.identifier}'] = signature;
signedKeys.add(signedKey); signedKeys.add(signedKey);
}; }
for (final key in keys) { for (final key in keys) {
if (key.userId == client.userID) { if (key.userId == client.userID) {

View File

@ -21,14 +21,14 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../matrix.dart'; import 'package:matrix/encryption/cross_signing.dart';
import '../src/utils/run_in_root.dart'; import 'package:matrix/encryption/key_manager.dart';
import 'cross_signing.dart'; import 'package:matrix/encryption/key_verification_manager.dart';
import 'key_manager.dart'; import 'package:matrix/encryption/olm_manager.dart';
import 'key_verification_manager.dart'; import 'package:matrix/encryption/ssss.dart';
import 'olm_manager.dart'; import 'package:matrix/encryption/utils/bootstrap.dart';
import 'ssss.dart'; import 'package:matrix/matrix.dart';
import 'utils/bootstrap.dart'; import 'package:matrix/src/utils/run_in_root.dart';
class Encryption { class Encryption {
final Client client; final Client client;
@ -206,10 +206,9 @@ class Encryption {
canRequestSession = false; canRequestSession = false;
// we can't have the key be an int, else json-serializing will fail, thus we need it to be a string // we can't have the key be an int, else json-serializing will fail, thus we need it to be a string
final messageIndexKey = 'key-' + decryptResult.message_index.toString(); final messageIndexKey = 'key-${decryptResult.message_index}';
final messageIndexValue = event.eventId + final messageIndexValue =
'|' + '${event.eventId}|${event.originServerTs.millisecondsSinceEpoch}';
event.originServerTs.millisecondsSinceEpoch.toString();
final haveIndex = final haveIndex =
inboundGroupSession.indexes.containsKey(messageIndexKey); inboundGroupSession.indexes.containsKey(messageIndexKey);
if (haveIndex && if (haveIndex &&

View File

@ -22,13 +22,13 @@ import 'dart:convert';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'package:matrix/encryption/utils/outbound_group_session.dart';
import 'package:matrix/encryption/utils/session_key.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import '../matrix.dart'; import 'package:matrix/matrix.dart';
import '../src/utils/run_in_root.dart'; import 'package:matrix/src/utils/run_in_root.dart';
import './encryption.dart';
import './utils/outbound_group_session.dart';
import './utils/session_key.dart';
const megolmKey = EventTypes.MegolmBackup; const megolmKey = EventTypes.MegolmBackup;
@ -445,13 +445,17 @@ class KeyManager {
if (sess != null) { if (sess != null) {
return sess; return sess;
} }
_pendingNewOutboundGroupSessions[roomId] = final newSess = _pendingNewOutboundGroupSessions[roomId] =
_createOutboundGroupSession(roomId); _createOutboundGroupSession(roomId);
try { try {
await _pendingNewOutboundGroupSessions[roomId]; await newSess;
} finally { } finally {
return _pendingNewOutboundGroupSessions.remove(roomId)!; _pendingNewOutboundGroupSessions
.removeWhere((_, value) => value == newSess);
} }
return newSess;
} }
/// Prepares an outbound group session for a given room ID. That is, load it from /// Prepares an outbound group session for a given room ID. That is, load it from
@ -794,7 +798,7 @@ class KeyManager {
/// Handle an incoming to_device event that is related to key sharing /// Handle an incoming to_device event that is related to key sharing
Future<void> handleToDeviceEvent(ToDeviceEvent event) async { Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
if (event.type == EventTypes.RoomKeyRequest) { if (event.type == EventTypes.RoomKeyRequest) {
if (!(event.content['request_id'] is String)) { if (event.content['request_id'] is! String) {
return; // invalid event return; // invalid event
} }
if (event.content['action'] == 'request') { if (event.content['action'] == 'request') {
@ -899,7 +903,7 @@ class KeyManager {
return; // someone we didn't send our request to replied....better ignore this return; // someone we didn't send our request to replied....better ignore this
} }
// we add the sender key to the forwarded key chain // we add the sender key to the forwarded key chain
if (!(event.content['forwarding_curve25519_key_chain'] is List)) { if (event.content['forwarding_curve25519_key_chain'] is! List) {
event.content['forwarding_curve25519_key_chain'] = <String>[]; event.content['forwarding_curve25519_key_chain'] = <String>[];
} }
event.content['forwarding_curve25519_key_chain'] event.content['forwarding_curve25519_key_chain']

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../matrix.dart'; import 'package:matrix/encryption/encryption.dart';
import 'encryption.dart'; import 'package:matrix/encryption/utils/key_verification.dart';
import 'utils/key_verification.dart'; import 'package:matrix/matrix.dart';
class KeyVerificationManager { class KeyVerificationManager {
final Encryption encryption; final Encryption encryption;

View File

@ -23,11 +23,11 @@ import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/json_signature_check_extension.dart';
import 'package:matrix/encryption/utils/olm_session.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../encryption/utils/json_signature_check_extension.dart'; import 'package:matrix/src/utils/run_in_root.dart';
import '../src/utils/run_in_root.dart';
import 'encryption.dart';
import 'utils/olm_session.dart';
class OlmManager { class OlmManager {
final Encryption encryption; final Encryption encryption;
@ -119,8 +119,8 @@ class OlmManager {
bool updateDatabase = true, bool updateDatabase = true,
bool? unusedFallbackKey = false, bool? unusedFallbackKey = false,
}) async { }) async {
final _olmAccount = this._olmAccount; final olmAccount = _olmAccount;
if (_olmAccount == null) { if (olmAccount == null) {
return true; return true;
} }
@ -136,22 +136,22 @@ class OlmManager {
// check if we have OTKs that still need uploading. If we do, we don't try to generate new ones, // check if we have OTKs that still need uploading. If we do, we don't try to generate new ones,
// instead we try to upload the old ones first // instead we try to upload the old ones first
final oldOTKsNeedingUpload = json final oldOTKsNeedingUpload = json
.decode(_olmAccount.one_time_keys())['curve25519'] .decode(olmAccount.one_time_keys())['curve25519']
.entries .entries
.length as int; .length as int;
// generate one-time keys // generate one-time keys
// we generate 2/3rds of max, so that other keys people may still have can // we generate 2/3rds of max, so that other keys people may still have can
// still be used // still be used
final oneTimeKeysCount = final oneTimeKeysCount =
(_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() - (olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
oldKeyCount - oldKeyCount -
oldOTKsNeedingUpload; oldOTKsNeedingUpload;
if (oneTimeKeysCount > 0) { if (oneTimeKeysCount > 0) {
_olmAccount.generate_one_time_keys(oneTimeKeysCount); olmAccount.generate_one_time_keys(oneTimeKeysCount);
} }
uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload; uploadedOneTimeKeysCount = oneTimeKeysCount + oldOTKsNeedingUpload;
final Map<String, dynamic> oneTimeKeys = final Map<String, dynamic> oneTimeKeys =
json.decode(_olmAccount.one_time_keys()); json.decode(olmAccount.one_time_keys());
// now sign all the one-time keys // now sign all the one-time keys
for (final entry in oneTimeKeys['curve25519'].entries) { for (final entry in oneTimeKeys['curve25519'].entries) {
@ -166,8 +166,8 @@ class OlmManager {
final signedFallbackKeys = <String, dynamic>{}; final signedFallbackKeys = <String, dynamic>{};
if (encryption.isMinOlmVersion(3, 2, 0) && unusedFallbackKey == false) { if (encryption.isMinOlmVersion(3, 2, 0) && unusedFallbackKey == false) {
// we don't have an unused fallback key uploaded....so let's change that! // we don't have an unused fallback key uploaded....so let's change that!
_olmAccount.generate_fallback_key(); olmAccount.generate_fallback_key();
final fallbackKey = json.decode(_olmAccount.fallback_key()); final fallbackKey = json.decode(olmAccount.fallback_key());
// now sign all the fallback keys // now sign all the fallback keys
for (final entry in fallbackKey['curve25519'].entries) { for (final entry in fallbackKey['curve25519'].entries) {
final key = entry.key; final key = entry.key;
@ -194,7 +194,7 @@ class OlmManager {
}; };
if (uploadDeviceKeys) { if (uploadDeviceKeys) {
final Map<String, dynamic> keys = final Map<String, dynamic> keys =
json.decode(_olmAccount.identity_keys()); json.decode(olmAccount.identity_keys());
for (final entry in keys.entries) { for (final entry in keys.entries) {
final algorithm = entry.key; final algorithm = entry.key;
final value = entry.value; final value = entry.value;
@ -228,7 +228,7 @@ class OlmManager {
} }
// mark the OTKs as published and save that to datbase // mark the OTKs as published and save that to datbase
_olmAccount.mark_keys_as_published(); olmAccount.mark_keys_as_published();
if (updateDatabase) { if (updateDatabase) {
await client.database?.updateClientKeys(pickledOlmAccount!); await client.database?.updateClientKeys(pickledOlmAccount!);
} }
@ -324,7 +324,8 @@ class OlmManager {
final device = client.userDeviceKeys[event.sender]?.deviceKeys.values final device = client.userDeviceKeys[event.sender]?.deviceKeys.values
.firstWhereOrNull((d) => d.curve25519Key == senderKey); .firstWhereOrNull((d) => d.curve25519Key == senderKey);
final existingSessions = olmSessions[senderKey]; final existingSessions = olmSessions[senderKey];
final updateSessionUsage = ([OlmSession? session]) => runInRoot(() async { Future<void> updateSessionUsage([OlmSession? session]) =>
runInRoot(() async {
if (session != null) { if (session != null) {
session.lastReceived = DateTime.now(); session.lastReceived = DateTime.now();
await storeOlmSession(session); await storeOlmSession(session);
@ -480,10 +481,11 @@ class OlmManager {
return event; return event;
} }
final senderKey = event.parsedRoomEncryptedContent.senderKey; final senderKey = event.parsedRoomEncryptedContent.senderKey;
final loadFromDb = () async { Future<bool> loadFromDb() async {
final sessions = await getOlmSessions(senderKey); final sessions = await getOlmSessions(senderKey);
return sessions.isNotEmpty; return sessions.isNotEmpty;
}; }
if (!_olmSessions.containsKey(senderKey)) { if (!_olmSessions.containsKey(senderKey)) {
await loadFromDb(); await loadFromDb();
} }

View File

@ -25,12 +25,12 @@ import 'package:base58check/base58.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:matrix/encryption/encryption.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import '../matrix.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart';
import '../src/utils/crypto/crypto.dart' as uc; import 'package:matrix/matrix.dart';
import '../src/utils/run_in_root.dart'; import 'package:matrix/src/utils/crypto/crypto.dart' as uc;
import 'encryption.dart'; import 'package:matrix/src/utils/run_in_root.dart';
import 'utils/ssss_cache.dart';
const cacheTypes = <String>{ const cacheTypes = <String>{
EventTypes.CrossSigningSelfSigning, EventTypes.CrossSigningSelfSigning,
@ -286,7 +286,7 @@ class SSSS {
if (keys == null) { if (keys == null) {
return null; return null;
} }
final isValid = (dbEntry) => bool isValid(dbEntry) =>
keys.contains(dbEntry.keyId) && keys.contains(dbEntry.keyId) &&
dbEntry.ciphertext != null && dbEntry.ciphertext != null &&
client.accountData[type]?.content['encrypted'][dbEntry.keyId] client.accountData[type]?.content['encrypted'][dbEntry.keyId]
@ -311,10 +311,10 @@ class SSSS {
if (secretInfo == null) { if (secretInfo == null) {
throw Exception('Not found'); throw Exception('Not found');
} }
if (!(secretInfo.content['encrypted'] is Map)) { if (secretInfo.content['encrypted'] is! Map) {
throw Exception('Content is not encrypted'); throw Exception('Content is not encrypted');
} }
if (!(secretInfo.content['encrypted'][keyId] is Map)) { if (secretInfo.content['encrypted'][keyId] is! Map) {
throw Exception('Wrong / unknown key'); throw Exception('Wrong / unknown key');
} }
final enc = secretInfo.content['encrypted'][keyId]; final enc = secretInfo.content['encrypted'][keyId];
@ -338,7 +338,7 @@ class SSSS {
Map<String, dynamic>? content; Map<String, dynamic>? content;
if (add && client.accountData[type] != null) { if (add && client.accountData[type] != null) {
content = client.accountData[type]!.content.copy(); content = client.accountData[type]!.content.copy();
if (!(content['encrypted'] is Map)) { if (content['encrypted'] is! Map) {
content['encrypted'] = <String, dynamic>{}; content['encrypted'] = <String, dynamic>{};
} }
} }
@ -526,7 +526,7 @@ class SSSS {
return; // someone replied whom we didn't send the share request to return; // someone replied whom we didn't send the share request to
} }
final secret = event.content['secret']; final secret = event.content['secret'];
if (!(event.content['secret'] is String)) { if (event.content['secret'] is! String) {
Logs().i('[SSSS] Secret wasn\'t a string'); Logs().i('[SSSS] Secret wasn\'t a string');
return; // the secret wasn't a string....wut? return; // the secret wasn't a string....wut?
} }

View File

@ -22,11 +22,11 @@ import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../../matrix.dart'; import 'package:matrix/encryption/encryption.dart';
import '../encryption.dart'; import 'package:matrix/encryption/key_manager.dart';
import '../key_manager.dart'; import 'package:matrix/encryption/ssss.dart';
import '../ssss.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'base64_unpadded.dart'; import 'package:matrix/matrix.dart';
enum BootstrapState { enum BootstrapState {
/// Is loading. /// Is loading.
@ -104,7 +104,7 @@ class Bootstrap {
for (final entry in client.accountData.entries) { for (final entry in client.accountData.entries) {
final type = entry.key; final type = entry.key;
final event = entry.value; final event = entry.value;
if (!(event.content['encrypted'] is Map)) { if (event.content['encrypted'] is! Map) {
continue; continue;
} }
final validKeys = <String>{}; final validKeys = <String>{};
@ -112,13 +112,13 @@ class Bootstrap {
for (final keyEntry in event.content['encrypted'].entries) { for (final keyEntry in event.content['encrypted'].entries) {
final key = keyEntry.key; final key = keyEntry.key;
final value = keyEntry.value; final value = keyEntry.value;
if (!(value is Map)) { if (value is! Map) {
// we don't add the key to invalidKeys as this was not a proper secret anyways! // we don't add the key to invalidKeys as this was not a proper secret anyways!
continue; continue;
} }
if (!(value['iv'] is String) || if (value['iv'] is! String ||
!(value['ciphertext'] is String) || value['ciphertext'] is! String ||
!(value['mac'] is String)) { value['mac'] is! String) {
invalidKeys.add(key); invalidKeys.add(key);
continue; continue;
} }
@ -163,11 +163,12 @@ class Bootstrap {
(k, v) => v.isEmpty); // we don't care about the failed secrets here (k, v) => v.isEmpty); // we don't care about the failed secrets here
final keys = <String>{}; final keys = <String>{};
final defaultKeyId = encryption.ssss.defaultKeyId; final defaultKeyId = encryption.ssss.defaultKeyId;
final removeKey = (String key) { int removeKey(String key) {
final sizeBefore = secrets.length; final sizeBefore = secrets.length;
secrets.removeWhere((k, v) => v.contains(key)); secrets.removeWhere((k, v) => v.contains(key));
return sizeBefore - secrets.length; return sizeBefore - secrets.length;
}; }
// first we want to try the default key id // first we want to try the default key id
if (defaultKeyId != null) { if (defaultKeyId != null) {
if (removeKey(defaultKeyId) > 0) { if (removeKey(defaultKeyId) > 0) {
@ -264,14 +265,15 @@ class Bootstrap {
if (oldSsssKeys != null) { if (oldSsssKeys != null) {
// alright, we have to re-encrypt old secrets with the new key // alright, we have to re-encrypt old secrets with the new key
final secrets = analyzeSecrets(); final secrets = analyzeSecrets();
final removeKey = (String key) { Set<String> removeKey(String key) {
final s = secrets.entries final s = secrets.entries
.where((e) => e.value.contains(key)) .where((e) => e.value.contains(key))
.map((e) => e.key) .map((e) => e.key)
.toSet(); .toSet();
secrets.removeWhere((k, v) => v.contains(key)); secrets.removeWhere((k, v) => v.contains(key));
return s; return s;
}; }
secretMap = <String, String>{}; secretMap = <String, String>{};
for (final entry in oldSsssKeys!.entries) { for (final entry in oldSsssKeys!.entries) {
final key = entry.value; final key = entry.value;
@ -400,7 +402,7 @@ class Bootstrap {
master.free(); master.free();
} }
} }
final _sign = (Map<String, dynamic> object) { String? sign(Map<String, dynamic> object) {
final keyObj = olm.PkSigning(); final keyObj = olm.PkSigning();
try { try {
keyObj.init_with_seed(masterSigningKey); keyObj.init_with_seed(masterSigningKey);
@ -409,7 +411,8 @@ class Bootstrap {
} finally { } finally {
keyObj.free(); keyObj.free();
} }
}; }
if (setupSelfSigningKey) { if (setupSelfSigningKey) {
final selfSigning = olm.PkSigning(); final selfSigning = olm.PkSigning();
try { try {
@ -422,7 +425,7 @@ class Bootstrap {
'ed25519:$selfSigningPub': selfSigningPub, 'ed25519:$selfSigningPub': selfSigningPub,
}, },
}; };
final signature = _sign(json); final signature = sign(json);
json['signatures'] = <String, dynamic>{ json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{ userID: <String, dynamic>{
'ed25519:$masterPub': signature, 'ed25519:$masterPub': signature,
@ -447,7 +450,7 @@ class Bootstrap {
'ed25519:$userSigningPub': userSigningPub, 'ed25519:$userSigningPub': userSigningPub,
}, },
}; };
final signature = _sign(json); final signature = sign(json);
json['signatures'] = <String, dynamic>{ json['signatures'] = <String, dynamic>{
userID: <String, dynamic>{ userID: <String, dynamic>{
'ed25519:$masterPub': signature, 'ed25519:$masterPub': signature,

View File

@ -19,14 +19,14 @@
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
extension JsonSignatureCheckExtension on Map<String, dynamic> { extension JsonSignatureCheckExtension on Map<String, dynamic> {
/// Checks the signature of a signed json object. /// Checks the signature of a signed json object.
bool checkJsonSignature(String key, String userId, String deviceId) { bool checkJsonSignature(String key, String userId, String deviceId) {
final signatures = this['signatures']; final signatures = this['signatures'];
if (signatures == null || if (signatures == null ||
!(signatures is Map<String, dynamic>) || signatures is! Map<String, dynamic> ||
!signatures.containsKey(userId)) return false; !signatures.containsKey(userId)) return false;
remove('unsigned'); remove('unsigned');
remove('signatures'); remove('signatures');

View File

@ -22,8 +22,8 @@ import 'dart:typed_data';
import 'package:canonical_json/canonical_json.dart'; import 'package:canonical_json/canonical_json.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../../matrix.dart'; import 'package:matrix/encryption/encryption.dart';
import '../encryption.dart'; import 'package:matrix/matrix.dart';
/* /*
+-------------+ +-----------+ +-------------+ +-----------+
@ -112,7 +112,7 @@ class KeyVerification {
String? get deviceId => _deviceId; String? get deviceId => _deviceId;
String? _deviceId; String? _deviceId;
bool startedVerification = false; bool startedVerification = false;
_KeyVerificationMethod? method; _KeyVerificationMethod? _method;
List<String> possibleMethods = []; List<String> possibleMethods = [];
Map<String, dynamic>? startPayload; Map<String, dynamic>? startPayload;
String? _nextAction; String? _nextAction;
@ -140,7 +140,7 @@ class KeyVerification {
void dispose() { void dispose() {
Logs().i('[Key Verification] disposing object...'); Logs().i('[Key Verification] disposing object...');
method?.dispose(); _method?.dispose();
} }
static String? getTransactionId(Map<String, dynamic> payload) { static String? getTransactionId(Map<String, dynamic> payload) {
@ -194,7 +194,7 @@ class KeyVerification {
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
} }
_handlePayloadLock = true; _handlePayloadLock = true;
Logs().i('[Key Verification] Received type $type: ' + payload.toString()); Logs().i('[Key Verification] Received type $type: $payload');
try { try {
var thisLastStep = lastStep; var thisLastStep = lastStep;
switch (type) { switch (type) {
@ -272,17 +272,17 @@ class KeyVerification {
// as such, we better set it *before* we send our start // as such, we better set it *before* we send our start
lastStep = type; lastStep = type;
// TODO: Pick method? // TODO: Pick method?
final method = this.method = final method =
_makeVerificationMethod(possibleMethods.first, this); _method = _makeVerificationMethod(possibleMethods.first, this);
await method.sendStart(); await method.sendStart();
setState(KeyVerificationState.waitingAccept); setState(KeyVerificationState.waitingAccept);
break; break;
case EventTypes.KeyVerificationStart: case EventTypes.KeyVerificationStart:
_deviceId ??= payload['from_device']; _deviceId ??= payload['from_device'];
transactionId ??= eventId ?? payload['transaction_id']; transactionId ??= eventId ?? payload['transaction_id'];
if (method != null) { if (_method != null) {
// the other side sent us a start, even though we already sent one // the other side sent us a start, even though we already sent one
if (payload['method'] == method!.type) { if (payload['method'] == _method!.type) {
// same method. Determine priority // same method. Determine priority
final ourEntry = '${client.userID}|${client.deviceID}'; final ourEntry = '${client.userID}|${client.deviceID}';
final entries = [ourEntry, '$userId|$deviceId']; final entries = [ourEntry, '$userId|$deviceId'];
@ -295,7 +295,7 @@ class KeyVerification {
startedVerification = false; // it is now as if they started startedVerification = false; // it is now as if they started
thisLastStep = lastStep = thisLastStep = lastStep =
EventTypes.KeyVerificationRequest; // we fake the last step EventTypes.KeyVerificationRequest; // we fake the last step
method!.dispose(); // in case anything got created already _method!.dispose(); // in case anything got created already
} }
} else { } else {
// methods don't match up, let's cancel this // methods don't match up, let's cancel this
@ -321,7 +321,7 @@ class KeyVerification {
} }
} }
method = _makeVerificationMethod(payload['method'], this); _method = _makeVerificationMethod(payload['method'], this);
if (lastStep == null) { if (lastStep == null) {
// validate the start time // validate the start time
if (room != null) { if (room != null) {
@ -330,7 +330,7 @@ class KeyVerification {
return; return;
} }
// validate the specific payload // validate the specific payload
if (!method!.validateStart(payload)) { if (!_method!.validateStart(payload)) {
await cancel('m.unknown_method'); await cancel('m.unknown_method');
return; return;
} }
@ -338,7 +338,7 @@ class KeyVerification {
setState(KeyVerificationState.askAccept); setState(KeyVerificationState.askAccept);
} else { } else {
Logs().i('handling start in method.....'); Logs().i('handling start in method.....');
await method!.handlePayload(type, payload); await _method!.handlePayload(type, payload);
} }
break; break;
case EventTypes.KeyVerificationDone: case EventTypes.KeyVerificationDone:
@ -351,7 +351,7 @@ class KeyVerification {
setState(KeyVerificationState.error); setState(KeyVerificationState.error);
break; break;
default: default:
final method = this.method; final method = _method;
if (method != null) { if (method != null) {
await method.handlePayload(type, payload); await method.handlePayload(type, payload);
} else { } else {
@ -382,7 +382,7 @@ class KeyVerification {
String? recoveryKey, String? recoveryKey,
String? keyOrPassphrase, String? keyOrPassphrase,
bool skip = false}) async { bool skip = false}) async {
final next = () async { Future<void> next() async {
if (_nextAction == 'request') { if (_nextAction == 'request') {
await sendStart(); await sendStart();
} else if (_nextAction == 'done') { } else if (_nextAction == 'done') {
@ -390,7 +390,8 @@ class KeyVerification {
unawaited(encryption.crossSigning.sign(_verifiedDevices)); unawaited(encryption.crossSigning.sign(_verifiedDevices));
setState(KeyVerificationState.done); setState(KeyVerificationState.done);
} }
}; }
if (skip) { if (skip) {
await next(); await next();
return; return;
@ -420,7 +421,7 @@ class KeyVerification {
}); });
} else { } else {
// we need to send an accept event // we need to send an accept event
await method! await _method!
.handlePayload(EventTypes.KeyVerificationStart, startPayload!); .handlePayload(EventTypes.KeyVerificationStart, startPayload!);
} }
} }
@ -440,20 +441,20 @@ class KeyVerification {
} }
Future<void> acceptSas() async { Future<void> acceptSas() async {
if (method is _KeyVerificationMethodSas) { if (_method is _KeyVerificationMethodSas) {
await (method as _KeyVerificationMethodSas).acceptSas(); await (_method as _KeyVerificationMethodSas).acceptSas();
} }
} }
Future<void> rejectSas() async { Future<void> rejectSas() async {
if (method is _KeyVerificationMethodSas) { if (_method is _KeyVerificationMethodSas) {
await (method as _KeyVerificationMethodSas).rejectSas(); await (_method as _KeyVerificationMethodSas).rejectSas();
} }
} }
List<int> get sasNumbers { List<int> get sasNumbers {
if (method is _KeyVerificationMethodSas) { if (_method is _KeyVerificationMethodSas) {
return _bytesToInt((method as _KeyVerificationMethodSas).makeSas(5), 13) return _bytesToInt((_method as _KeyVerificationMethodSas).makeSas(5), 13)
.map((n) => n + 1000) .map((n) => n + 1000)
.toList(); .toList();
} }
@ -461,16 +462,16 @@ class KeyVerification {
} }
List<String> get sasTypes { List<String> get sasTypes {
if (method is _KeyVerificationMethodSas) { if (_method is _KeyVerificationMethodSas) {
return (method as _KeyVerificationMethodSas).authenticationTypes ?? []; return (_method as _KeyVerificationMethodSas).authenticationTypes ?? [];
} }
return []; return [];
} }
List<KeyVerificationEmoji> get sasEmojis { List<KeyVerificationEmoji> get sasEmojis {
if (method is _KeyVerificationMethodSas) { if (_method is _KeyVerificationMethodSas) {
final numbers = final numbers =
_bytesToInt((method as _KeyVerificationMethodSas).makeSas(6), 6); _bytesToInt((_method as _KeyVerificationMethodSas).makeSas(6), 6);
return numbers.map((n) => KeyVerificationEmoji(n)).toList().sublist(0, 7); return numbers.map((n) => KeyVerificationEmoji(n)).toList().sublist(0, 7);
} }
return []; return [];
@ -605,7 +606,7 @@ class KeyVerification {
Future<void> send(String type, Map<String, dynamic> payload) async { Future<void> send(String type, Map<String, dynamic> payload) async {
makePayload(payload); makePayload(payload);
Logs().i('[Key Verification] Sending type $type: ' + payload.toString()); Logs().i('[Key Verification] Sending type $type: $payload');
if (room != null) { if (room != null) {
Logs().i('[Key Verification] Sending to $userId in room ${room!.id}...'); Logs().i('[Key Verification] Sending to $userId in room ${room!.id}...');
if ({EventTypes.KeyVerificationRequest}.contains(type)) { if ({EventTypes.KeyVerificationRequest}.contains(type)) {
@ -680,6 +681,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
: super(request: request); : super(request: request);
@override @override
// ignore: overridden_fields
final _type = 'm.sas.v1'; final _type = 'm.sas.v1';
String? keyAgreementProtocol; String? keyAgreementProtocol;
@ -897,19 +899,13 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
'${client.userID}|${client.deviceID}|${sas!.get_pubkey()}|'; '${client.userID}|${client.deviceID}|${sas!.get_pubkey()}|';
final theirInfo = final theirInfo =
'${request.userId}|${request.deviceId}|$theirPublicKey|'; '${request.userId}|${request.deviceId}|$theirPublicKey|';
sasInfo = 'MATRIX_KEY_VERIFICATION_SAS|' + sasInfo =
(request.startedVerification 'MATRIX_KEY_VERIFICATION_SAS|${request.startedVerification ? ourInfo + theirInfo : theirInfo + ourInfo}${request.transactionId!}';
? ourInfo + theirInfo
: theirInfo + ourInfo) +
request.transactionId!;
} else if (keyAgreementProtocol == 'curve25519') { } else if (keyAgreementProtocol == 'curve25519') {
final ourInfo = client.userID! + client.deviceID!; final ourInfo = client.userID! + client.deviceID!;
final theirInfo = request.userId + request.deviceId!; final theirInfo = request.userId + request.deviceId!;
sasInfo = 'MATRIX_KEY_VERIFICATION_SAS' + sasInfo =
(request.startedVerification 'MATRIX_KEY_VERIFICATION_SAS${request.startedVerification ? ourInfo + theirInfo : theirInfo + ourInfo}${request.transactionId!}';
? ourInfo + theirInfo
: theirInfo + ourInfo) +
request.transactionId!;
} else { } else {
throw Exception('Unknown key agreement protocol'); throw Exception('Unknown key agreement protocol');
} }
@ -917,12 +913,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} }
Future<void> _sendMac() async { Future<void> _sendMac() async {
final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + final baseInfo =
client.userID! + 'MATRIX_KEY_VERIFICATION_MAC${client.userID!}${client.deviceID!}${request.userId}${request.deviceId!}${request.transactionId!}';
client.deviceID! +
request.userId +
request.deviceId! +
request.transactionId!;
final mac = <String, String>{}; final mac = <String, String>{};
final keyList = <String>[]; final keyList = <String>[];
@ -944,7 +936,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
} }
keyList.sort(); keyList.sort();
final keys = _calculateMac(keyList.join(','), baseInfo + 'KEY_IDS'); final keys = _calculateMac(keyList.join(','), '${baseInfo}KEY_IDS');
await request.send('m.key.verification.mac', { await request.send('m.key.verification.mac', {
'mac': mac, 'mac': mac,
'keys': keys, 'keys': keys,
@ -953,17 +945,13 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
Future<void> _processMac() async { Future<void> _processMac() async {
final payload = macPayload!; final payload = macPayload!;
final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + final baseInfo =
request.userId + 'MATRIX_KEY_VERIFICATION_MAC${request.userId}${request.deviceId!}${client.userID!}${client.deviceID!}${request.transactionId!}';
request.deviceId! +
client.userID! +
client.deviceID! +
request.transactionId!;
final keyList = payload['mac'].keys.toList(); final keyList = payload['mac'].keys.toList();
keyList.sort(); keyList.sort();
if (payload['keys'] != if (payload['keys'] !=
_calculateMac(keyList.join(','), baseInfo + 'KEY_IDS')) { _calculateMac(keyList.join(','), '${baseInfo}KEY_IDS')) {
await request.cancel('m.key_mismatch'); await request.cancel('m.key_mismatch');
return; return;
} }
@ -981,7 +969,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
await request.verifyKeys(mac, (String mac, SignableKey key) async { await request.verifyKeys(mac, (String mac, SignableKey key) async {
return mac == return mac ==
_calculateMac( _calculateMac(
key.ed25519Key!, baseInfo + 'ed25519:' + key.identifier!); key.ed25519Key!, '${baseInfo}ed25519:${key.identifier!}');
}); });
} }

View File

@ -18,7 +18,7 @@
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
class OlmSession { class OlmSession {
String identityKey; String identityKey;
@ -38,9 +38,8 @@ class OlmSession {
required this.lastReceived, required this.lastReceived,
}); });
OlmSession.fromJson(Map<String, dynamic> dbEntry, String key) OlmSession.fromJson(Map<String, dynamic> dbEntry, this.key)
: key = key, : identityKey = dbEntry['identity_key'] ?? '' {
identityKey = dbEntry['identity_key'] ?? '' {
session = olm.Session(); session = olm.Session();
try { try {
session!.unpickle(key, dbEntry['pickle']); session!.unpickle(key, dbEntry['pickle']);

View File

@ -20,7 +20,7 @@ import 'dart:convert';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
class OutboundGroupSession { class OutboundGroupSession {
/// The devices is a map from user id to device id to if the device is blocked. /// The devices is a map from user id to device id to if the device is blocked.
@ -41,8 +41,7 @@ class OutboundGroupSession {
required this.outboundGroupSession, required this.outboundGroupSession,
required this.key}); required this.key});
OutboundGroupSession.fromJson(Map<String, dynamic> dbEntry, String key) OutboundGroupSession.fromJson(Map<String, dynamic> dbEntry, this.key) {
: key = key {
try { try {
for (final entry in json.decode(dbEntry['device_ids']).entries) { for (final entry in json.decode(dbEntry['device_ids']).entries) {
devices[entry.key] = Map<String, bool>.from(entry.value); devices[entry.key] = Map<String, bool>.from(entry.value);
@ -50,8 +49,7 @@ class OutboundGroupSession {
} catch (e) { } catch (e) {
// devices is bad (old data), so just not use this session // devices is bad (old data), so just not use this session
Logs().i( Logs().i(
'[OutboundGroupSession] Session in database is old, not using it. ' + '[OutboundGroupSession] Session in database is old, not using it. $e');
e.toString());
return; return;
} }
outboundGroupSession = olm.OutboundGroupSession(); outboundGroupSession = olm.OutboundGroupSession();

View File

@ -20,7 +20,7 @@ import 'package:matrix_api_lite/src/utils/filter_map_extension.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
class SessionKey { class SessionKey {
/// The raw json content of the key /// The raw json content of the key
@ -74,9 +74,8 @@ class SessionKey {
: indexes = indexes ?? <String, String>{}, : indexes = indexes ?? <String, String>{},
allowedAtIndex = allowedAtIndex ?? <String, Map<String, int>>{}; allowedAtIndex = allowedAtIndex ?? <String, Map<String, int>>{};
SessionKey.fromDb(StoredInboundGroupSession dbEntry, String key) SessionKey.fromDb(StoredInboundGroupSession dbEntry, this.key)
: key = key, : content = Event.getMapFromPayload(dbEntry.content),
content = Event.getMapFromPayload(dbEntry.content),
indexes = Event.getMapFromPayload(dbEntry.indexes) indexes = Event.getMapFromPayload(dbEntry.indexes)
.catchMap((k, v) => MapEntry<String, String>(k, v)), .catchMap((k, v) => MapEntry<String, String>(k, v)),
allowedAtIndex = Event.getMapFromPayload(dbEntry.allowedAtIndex) allowedAtIndex = Event.getMapFromPayload(dbEntry.allowedAtIndex)

View File

@ -19,7 +19,7 @@ extension MatrixWidgets on Room {
Future<String> addWidget(MatrixWidget widget) { Future<String> addWidget(MatrixWidget widget) {
final user = client.userID; final user = client.userID;
final widgetId = final widgetId =
widget.name!.toLowerCase().replaceAll(RegExp(r'\W'), '_') + '_' + user!; '${widget.name!.toLowerCase().replaceAll(RegExp(r'\W'), '_')}_${user!}';
final json = widget.toJson(); final json = widget.toJson();
json['creatorUserId'] = user; json['creatorUserId'] = user;

View File

@ -27,15 +27,15 @@ import 'package:mime/mime.dart';
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:random_string/random_string.dart'; import 'package:random_string/random_string.dart';
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/models/timeline_chunk.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/compute_callback.dart';
import 'package:matrix/src/utils/multilock.dart';
import 'package:matrix/src/utils/run_benchmarked.dart';
import 'package:matrix/src/utils/run_in_root.dart'; import 'package:matrix/src/utils/run_in_root.dart';
import 'package:matrix/src/utils/sync_update_item_count.dart'; import 'package:matrix/src/utils/sync_update_item_count.dart';
import '../encryption.dart';
import '../matrix.dart';
import 'models/timeline_chunk.dart';
import 'utils/compute_callback.dart';
import 'utils/multilock.dart';
import 'utils/run_benchmarked.dart';
typedef RoomSorter = int Function(Room a, Room b); typedef RoomSorter = int Function(Room a, Room b);
@ -940,28 +940,30 @@ class Client extends MatrixApi {
try { try {
// stopping sync loop and subscriptions while keeping DB open // stopping sync loop and subscriptions while keeping DB open
await dispose(closeDatabase: false); await dispose(closeDatabase: false);
} finally { } catch (_) {
_database ??= await databaseBuilder!.call(this); // Client was probably not initialized yet.
final success = await database!.importDump(export);
if (success) {
// closing including DB
await dispose();
try {
bearerToken = null;
await init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
);
} catch (e) {
return false;
}
}
return success;
} }
_database ??= await databaseBuilder!.call(this);
final success = await database!.importDump(export);
if (success) {
// closing including DB
await dispose();
try {
bearerToken = null;
await init(
waitForFirstSync: false,
waitUntilLoadCompletedLoaded: false,
);
} catch (e) {
return false;
}
}
return success;
} }
/// Uploads a new user avatar for this user. Leave file null to remove the /// Uploads a new user avatar for this user. Leave file null to remove the
@ -1336,13 +1338,13 @@ class Client extends MatrixApi {
String? olmAccount; String? olmAccount;
String? accessToken; String? accessToken;
String? _userID; String? userID;
final account = await this.database?.getClient(clientName); final account = await this.database?.getClient(clientName);
if (account != null) { if (account != null) {
_id = account['client_id']; _id = account['client_id'];
homeserver = Uri.parse(account['homeserver_url']); homeserver = Uri.parse(account['homeserver_url']);
accessToken = this.accessToken = account['token']; accessToken = this.accessToken = account['token'];
_userID = this._userID = account['user_id']; userID = _userID = account['user_id'];
_deviceID = account['device_id']; _deviceID = account['device_id'];
_deviceName = account['device_name']; _deviceName = account['device_name'];
syncFilterId = account['sync_filter_id']; syncFilterId = account['sync_filter_id'];
@ -1352,20 +1354,20 @@ class Client extends MatrixApi {
if (newToken != null) { if (newToken != null) {
accessToken = this.accessToken = newToken; accessToken = this.accessToken = newToken;
homeserver = newHomeserver; homeserver = newHomeserver;
_userID = this._userID = newUserID; userID = _userID = newUserID;
_deviceID = newDeviceID; _deviceID = newDeviceID;
_deviceName = newDeviceName; _deviceName = newDeviceName;
olmAccount = newOlmAccount; olmAccount = newOlmAccount;
} else { } else {
accessToken = this.accessToken = newToken ?? accessToken; accessToken = this.accessToken = newToken ?? accessToken;
homeserver = newHomeserver ?? homeserver; homeserver = newHomeserver ?? homeserver;
_userID = this._userID = newUserID ?? _userID; userID = _userID = newUserID ?? userID;
_deviceID = newDeviceID ?? _deviceID; _deviceID = newDeviceID ?? _deviceID;
_deviceName = newDeviceName ?? _deviceName; _deviceName = newDeviceName ?? _deviceName;
olmAccount = newOlmAccount ?? olmAccount; olmAccount = newOlmAccount ?? olmAccount;
} }
if (accessToken == null || homeserver == null || _userID == null) { if (accessToken == null || homeserver == null || userID == null) {
if (legacyDatabaseBuilder != null) { if (legacyDatabaseBuilder != null) {
await _migrateFromLegacyDatabase(); await _migrateFromLegacyDatabase();
if (isLogged()) return; if (isLogged()) return;
@ -1397,7 +1399,7 @@ class Client extends MatrixApi {
await database.updateClient( await database.updateClient(
homeserver.toString(), homeserver.toString(),
accessToken, accessToken,
_userID, userID,
_deviceID, _deviceID,
_deviceName, _deviceName,
prevBatch, prevBatch,
@ -1408,7 +1410,7 @@ class Client extends MatrixApi {
clientName, clientName,
homeserver.toString(), homeserver.toString(),
accessToken, accessToken,
_userID, userID,
_deviceID, _deviceID,
_deviceName, _deviceName,
prevBatch, prevBatch,
@ -1434,7 +1436,7 @@ class Client extends MatrixApi {
_initLock = false; _initLock = false;
onLoginStateChanged.add(LoginState.loggedIn); onLoginStateChanged.add(LoginState.loggedIn);
Logs().i( Logs().i(
'Successfully connected as ${userID?.localpart} with ${homeserver.toString()}', 'Successfully connected as ${userID.localpart} with ${homeserver.toString()}',
); );
final syncFuture = _sync(); final syncFuture = _sync();
@ -1508,13 +1510,13 @@ class Client extends MatrixApi {
} }
Future<void> _sync() { Future<void> _sync() {
final _currentSync = this._currentSync ??= _innerSync().whenComplete(() { final currentSync = _currentSync ??= _innerSync().whenComplete(() {
this._currentSync = null; _currentSync = null;
if (_backgroundSync && isLogged() && !_disposed) { if (_backgroundSync && isLogged() && !_disposed) {
_sync(); _sync();
} }
}); });
return _currentSync; return currentSync;
} }
/// Presence that is set on sync. /// Presence that is set on sync.
@ -1533,13 +1535,13 @@ class Client extends MatrixApi {
Future<void> _innerSync() async { Future<void> _innerSync() async {
await _retryDelay; await _retryDelay;
_retryDelay = Future.delayed(Duration(seconds: syncErrorTimeoutSec)); _retryDelay = Future.delayed(Duration(seconds: syncErrorTimeoutSec));
if (!isLogged() || _disposed || _aborted) return null; if (!isLogged() || _disposed || _aborted) return;
try { try {
if (_initLock) { if (_initLock) {
Logs().d('Running sync while init isn\'t done yet, dropping request'); Logs().d('Running sync while init isn\'t done yet, dropping request');
return; return;
} }
var syncError; dynamic syncError;
await _checkSyncFilter(); await _checkSyncFilter();
final syncRequest = sync( final syncRequest = sync(
filter: syncFilterId, filter: syncFilterId,

View File

@ -22,8 +22,8 @@ import 'package:matrix/encryption/utils/olm_session.dart';
import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart';
import 'package:matrix/encryption/utils/ssss_cache.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart';
import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/queued_to_device_event.dart'; import 'package:matrix/src/utils/queued_to_device_event.dart';
import '../../matrix.dart';
abstract class DatabaseApi { abstract class DatabaseApi {
int get maxFileSize => 1 * 1024 * 1024; int get maxFileSize => 1 * 1024 * 1024;
@ -154,7 +154,7 @@ abstract class DatabaseApi {
); );
Future storeOlmSession( Future storeOlmSession(
String identitiyKey, String identityKey,
String sessionId, String sessionId,
String pickle, String pickle,
int lastReceived, int lastReceived,

View File

@ -448,9 +448,9 @@ class FluffyBoxDatabase extends DatabaseApi {
@override @override
Future<List<OlmSession>> getOlmSessionsForDevices( Future<List<OlmSession>> getOlmSessionsForDevices(
List<String> identityKey, String userId) async { List<String> identityKeys, String userId) async {
final sessions = await Future.wait( final sessions = await Future.wait(
identityKey.map((identityKey) => getOlmSessions(identityKey, userId))); identityKeys.map((identityKey) => getOlmSessions(identityKey, userId)));
return <OlmSession>[for (final sublist in sessions) ...sublist]; return <OlmSession>[for (final sublist in sessions) ...sublist];
} }
@ -1433,10 +1433,9 @@ class FluffyBoxDatabase extends DatabaseApi {
Future<void> addSeenDeviceId( Future<void> addSeenDeviceId(
String userId, String userId,
String deviceId, String deviceId,
String publicKeysHash, String publicKeys,
) => ) =>
_seenDeviceIdsBox.put( _seenDeviceIdsBox.put(TupleKey(userId, deviceId).toString(), publicKeys);
TupleKey(userId, deviceId).toString(), publicKeysHash);
@override @override
Future<void> addSeenPublicKey( Future<void> addSeenPublicKey(

View File

@ -474,9 +474,9 @@ class HiveCollectionsDatabase extends DatabaseApi {
@override @override
Future<List<OlmSession>> getOlmSessionsForDevices( Future<List<OlmSession>> getOlmSessionsForDevices(
List<String> identityKey, String userId) async { List<String> identityKeys, String userId) async {
final sessions = await Future.wait( final sessions = await Future.wait(
identityKey.map((identityKey) => getOlmSessions(identityKey, userId))); identityKeys.map((identityKey) => getOlmSessions(identityKey, userId)));
return <OlmSession>[for (final sublist in sessions) ...sublist]; return <OlmSession>[for (final sublist in sessions) ...sublist];
} }
@ -1459,10 +1459,9 @@ class HiveCollectionsDatabase extends DatabaseApi {
Future<void> addSeenDeviceId( Future<void> addSeenDeviceId(
String userId, String userId,
String deviceId, String deviceId,
String publicKeysHash, String publicKeys,
) => ) =>
_seenDeviceIdsBox.put( _seenDeviceIdsBox.put(TupleKey(userId, deviceId).toString(), publicKeys);
TupleKey(userId, deviceId).toString(), publicKeysHash);
@override @override
Future<void> addSeenPublicKey( Future<void> addSeenPublicKey(
@ -1611,6 +1610,9 @@ class TupleKey {
@override @override
bool operator ==(other) => parts.toString() == other.toString(); bool operator ==(other) => parts.toString() == other.toString();
@override
int get hashCode => Object.hashAll(parts);
} }
dynamic _castValue(dynamic value) { dynamic _castValue(dynamic value) {

View File

@ -505,9 +505,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
@override @override
Future<List<OlmSession>> getOlmSessionsForDevices( Future<List<OlmSession>> getOlmSessionsForDevices(
List<String> identityKey, String userId) async { List<String> identityKeys, String userId) async {
final sessions = await Future.wait( final sessions = await Future.wait(
identityKey.map((identityKey) => getOlmSessions(identityKey, userId))); identityKeys.map((identityKey) => getOlmSessions(identityKey, userId)));
return <OlmSession>[for (final sublist in sessions) ...sublist]; return <OlmSession>[for (final sublist in sessions) ...sublist];
} }
@ -1380,10 +1380,9 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
Future<void> addSeenDeviceId( Future<void> addSeenDeviceId(
String userId, String userId,
String deviceId, String deviceId,
String publicKeysHash, String publicKeys,
) => ) =>
_seenDeviceIdsBox.put( _seenDeviceIdsBox.put(MultiKey(userId, deviceId).toString(), publicKeys);
MultiKey(userId, deviceId).toString(), publicKeysHash);
@override @override
Future<void> addSeenPublicKey( Future<void> addSeenPublicKey(
@ -1461,6 +1460,9 @@ class MultiKey {
@override @override
bool operator ==(other) => parts.toString() == other.toString(); bool operator ==(other) => parts.toString() == other.toString();
@override
int get hashCode => Object.hashAll(parts);
} }
extension HiveKeyExtension on String { extension HiveKeyExtension on String {

View File

@ -23,11 +23,11 @@ import 'package:collection/collection.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/event_localizations.dart';
import 'package:matrix/src/utils/file_send_request_credentials.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart';
import '../matrix.dart'; import 'package:matrix/src/utils/html_to_text.dart';
import 'utils/event_localizations.dart'; import 'package:matrix/src/utils/markdown.dart';
import 'utils/html_to_text.dart';
import 'utils/markdown.dart';
abstract class RelationshipTypes { abstract class RelationshipTypes {
static const String reply = 'm.in_reply_to'; static const String reply = 'm.in_reply_to';
@ -296,7 +296,7 @@ class Event extends MatrixEvent {
if (redacted) return 'Redacted'; if (redacted) return 'Redacted';
if (text != '') return text; if (text != '') return text;
if (formattedText != '') return formattedText; if (formattedText != '') return formattedText;
return '$type'; return type;
} }
/// Use this to get a plain-text representation of the event, stripping things /// Use this to get a plain-text representation of the event, stripping things
@ -870,7 +870,7 @@ class Event extends MatrixEvent {
bool get onlyEmotes { bool get onlyEmotes {
if (isRichMessage) { if (isRichMessage) {
final formattedTextStripped = formattedText.replaceAll( final formattedTextStripped = formattedText.replaceAll(
RegExp('<mx-reply>.*<\/mx-reply>', RegExp('<mx-reply>.*</mx-reply>',
caseSensitive: false, multiLine: false, dotAll: true), caseSensitive: false, multiLine: false, dotAll: true),
''); '');
return _onlyEmojiEmoteRegex.hasMatch(formattedTextStripped); return _onlyEmojiEmoteRegex.hasMatch(formattedTextStripped);
@ -886,7 +886,7 @@ class Event extends MatrixEvent {
int get numberEmotes { int get numberEmotes {
if (isRichMessage) { if (isRichMessage) {
final formattedTextStripped = formattedText.replaceAll( final formattedTextStripped = formattedText.replaceAll(
RegExp('<mx-reply>.*<\/mx-reply>', RegExp('<mx-reply>.*</mx-reply>',
caseSensitive: false, multiLine: false, dotAll: true), caseSensitive: false, multiLine: false, dotAll: true),
''); '');
return _countEmojiEmoteRegex.allMatches(formattedTextStripped).length; return _countEmojiEmoteRegex.allMatches(formattedTextStripped).length;

View File

@ -1,4 +1,4 @@
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
class TimelineChunk { class TimelineChunk {
String prevBatch; // pos of the first event of the database timeline chunk String prevBatch; // pos of the first event of the database timeline chunk

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../matrix.dart'; import 'package:matrix/matrix.dart';
class CachedPresence { class CachedPresence {
PresenceType presence; PresenceType presence;
@ -45,9 +45,7 @@ class CachedPresence {
: this(event.presence, event.lastActiveAgo, event.statusMsg, : this(event.presence, event.lastActiveAgo, event.statusMsg,
event.currentlyActive, userid); event.currentlyActive, userid);
CachedPresence.neverSeen(String userid) CachedPresence.neverSeen(this.userid) : presence = PresenceType.offline;
: presence = PresenceType.offline,
userid = userid;
Presence toPresence() { Presence toPresence() {
final content = <String, dynamic>{ final content = <String, dynamic>{

View File

@ -23,14 +23,14 @@ import 'dart:typed_data';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:html_unescape/html_unescape.dart'; import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/models/timeline_chunk.dart'; import 'package:matrix/src/models/timeline_chunk.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/crypto/crypto.dart'; import 'package:matrix/src/utils/crypto/crypto.dart';
import 'package:matrix/src/utils/file_send_request_credentials.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart';
import 'package:matrix/src/utils/markdown.dart';
import 'package:matrix/src/utils/marked_unread.dart';
import 'package:matrix/src/utils/space_child.dart'; import 'package:matrix/src/utils/space_child.dart';
import '../matrix.dart';
import 'utils/markdown.dart';
import 'utils/marked_unread.dart';
enum PushRuleState { notify, mentionsOnly, dontNotify } enum PushRuleState { notify, mentionsOnly, dontNotify }
@ -907,7 +907,7 @@ class Room {
} }
inPrefix = false; inPrefix = false;
temp += temp.isEmpty ? l : ('\n' + l); temp += temp.isEmpty ? l : ('\n$l');
} }
return temp; return temp;
@ -936,7 +936,7 @@ class Room {
if (inReplyTo != null) { if (inReplyTo != null) {
var replyText = var replyText =
'<${inReplyTo.senderId}> ' + _stripBodyFallback(inReplyTo.body); '<${inReplyTo.senderId}> ${_stripBodyFallback(inReplyTo.body)}';
replyText = replyText.split('\n').map((line) => '> $line').join('\n'); replyText = replyText.split('\n').map((line) => '> $line').join('\n');
content['format'] = 'org.matrix.custom.html'; content['format'] = 'org.matrix.custom.html';
// be sure that we strip any previous reply fallbacks // be sure that we strip any previous reply fallbacks
@ -971,10 +971,10 @@ class Room {
'rel_type': RelationshipTypes.edit, 'rel_type': RelationshipTypes.edit,
}; };
if (content['body'] is String) { if (content['body'] is String) {
content['body'] = '* ' + content['body']; content['body'] = '* ${content['body']}';
} }
if (content['formatted_body'] is String) { if (content['formatted_body'] is String) {
content['formatted_body'] = '* ' + content['formatted_body']; content['formatted_body'] = '* ${content['formatted_body']}';
} }
} }
final sentDate = DateTime.now(); final sentDate = DateTime.now();
@ -1113,7 +1113,7 @@ class Room {
/// power level event, there might something broken and this returns null. /// power level event, there might something broken and this returns null.
Future<String> setPower(String userID, int power) async { Future<String> setPower(String userID, int power) async {
var powerMap = getState(EventTypes.RoomPowerLevels)?.content; var powerMap = getState(EventTypes.RoomPowerLevels)?.content;
if (!(powerMap is Map<String, dynamic>)) { if (powerMap is! Map<String, dynamic>) {
powerMap = <String, dynamic>{}; powerMap = <String, dynamic>{};
} }
(powerMap['users'] ??= {})[userID] = power; (powerMap['users'] ??= {})[userID] = power;
@ -1155,7 +1155,7 @@ class Room {
if (onHistoryReceived != null) onHistoryReceived(); if (onHistoryReceived != null) onHistoryReceived();
this.prev_batch = resp.end; this.prev_batch = resp.end;
final loadFn = () async { Future<void> loadFn() async {
if (!((resp.chunk.isNotEmpty) && resp.end != null)) return; if (!((resp.chunk.isNotEmpty) && resp.end != null)) return;
await client.handleSync( await client.handleSync(
@ -1196,7 +1196,7 @@ class Room {
: null), : null),
), ),
direction: Direction.b); direction: Direction.b);
}; }
if (client.database != null) { if (client.database != null) {
await client.database?.transaction(() async { await client.database?.transaction(() async {
@ -1331,7 +1331,7 @@ class Room {
String? eventContextId}) async { String? eventContextId}) async {
await postLoad(); await postLoad();
var events; List<Event> events;
if (!isArchived) { if (!isArchived) {
events = await client.database?.getEventList( events = await client.database?.getEventList(
@ -1762,7 +1762,7 @@ class Room {
PushRuleState get pushRuleState { PushRuleState get pushRuleState {
final globalPushRules = final globalPushRules =
client.accountData['m.push_rules']?.content['global']; client.accountData['m.push_rules']?.content['global'];
if (!(globalPushRules is Map)) { if (globalPushRules is! Map) {
return PushRuleState.notify; return PushRuleState.notify;
} }
@ -1794,7 +1794,7 @@ class Room {
/// Sends a request to the homeserver to set the [PushRuleState] for this room. /// Sends a request to the homeserver to set the [PushRuleState] for this room.
/// Returns ErrorResponse if something goes wrong. /// Returns ErrorResponse if something goes wrong.
Future<void> setPushRuleState(PushRuleState newState) async { Future<void> setPushRuleState(PushRuleState newState) async {
if (newState == pushRuleState) return null; if (newState == pushRuleState) return;
dynamic resp; dynamic resp;
switch (newState) { switch (newState) {
// All push notifications should be sent to the user // All push notifications should be sent to the user
@ -2076,6 +2076,9 @@ class Room {
@override @override
bool operator ==(dynamic other) => (other is Room && other.id == id); bool operator ==(dynamic other) => (other is Room && other.id == id);
@override
int get hashCode => Object.hashAll([id]);
} }
enum EncryptionHealthState { enum EncryptionHealthState {

View File

@ -19,10 +19,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:collection/src/iterable_extensions.dart'; import 'package:collection/collection.dart';
import '../matrix.dart'; import 'package:matrix/matrix.dart';
import 'models/timeline_chunk.dart'; import 'package:matrix/src/models/timeline_chunk.dart';
/// Represents the timeline of a room. The callback [onUpdate] will be triggered /// Represents the timeline of a room. The callback [onUpdate] will be triggered
/// automatically. The initial /// automatically. The initial
@ -305,7 +305,7 @@ class Timeline {
void _sessionKeyReceived(String sessionId) async { void _sessionKeyReceived(String sessionId) async {
var decryptAtLeastOneEvent = false; var decryptAtLeastOneEvent = false;
final decryptFn = () async { Future<void> decryptFn() async {
final encryption = room.client.encryption; final encryption = room.client.encryption;
if (!room.client.encryptionEnabled || encryption == null) { if (!room.client.encryptionEnabled || encryption == null) {
return; return;
@ -322,7 +322,8 @@ class Timeline {
} }
} }
} }
}; }
if (room.client.database != null) { if (room.client.database != null) {
await room.client.database?.transaction(decryptFn); await room.client.database?.transaction(decryptFn);
} else { } else {

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../matrix.dart'; import 'package:matrix/matrix.dart';
/// Represents a Matrix User which may be a participant in a Matrix Room. /// Represents a Matrix User which may be a participant in a Matrix Room.
class User extends Event { class User extends Event {
@ -80,7 +80,7 @@ class User extends Event {
/// ban /// ban
Membership get membership => Membership.values.firstWhere((e) { Membership get membership => Membership.values.firstWhere((e) {
if (content['membership'] != null) { if (content['membership'] != null) {
return e.toString() == 'Membership.' + content['membership']; return e.toString() == 'Membership.${content['membership']}';
} }
return false; return false;
}, orElse: () => Membership.join); }, orElse: () => Membership.join);
@ -197,6 +197,9 @@ class User extends Event {
other.room == room && other.room == room &&
other.membership == membership); other.membership == membership);
@override
int get hashCode => Object.hash(id, room, membership);
/// Get the mention text to use in a plain text body to mention this specific user /// Get the mention text to use in a plain text body to mention this specific user
/// in this specific room /// in this specific room
String get mention { String get mention {
@ -210,11 +213,8 @@ class User extends Event {
return id; return id;
} }
final identifier = '@' + final identifier =
// if we have non-word characters we need to surround with [] '@${RegExp(r'^\w+$').hasMatch(displayName) ? displayName : '[$displayName]'}';
(RegExp(r'^\w+$').hasMatch(displayName)
? displayName
: '[$displayName]');
// get all the users with the same display name // get all the users with the same display name
final allUsersWithSameDisplayname = room.getParticipants(); final allUsersWithSameDisplayname = room.getParticipants();
@ -243,11 +243,8 @@ class User extends Event {
{'[', ']', ':'}.any(displayName.contains)) { {'[', ']', ':'}.any(displayName.contains)) {
return {}; return {};
} }
final identifier = '@' + final identifier =
// if we have non-word characters we need to surround with [] '@${RegExp(r'^\w+$').hasMatch(displayName) ? displayName : '[$displayName]'}';
(RegExp(r'^\w+$').hasMatch(displayName)
? displayName
: '[$displayName]');
final hash = _hash(id); final hash = _hash(id);
return {identifier, '$identifier#$hash'}; return {identifier, '$identifier#$hash'};

View File

@ -18,7 +18,7 @@
import 'dart:async'; import 'dart:async';
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
extension CommandsClientExtension on Client { extension CommandsClientExtension on Client {
/// Add a command to the command handler. `command` is its name, and `callback` is the /// Add a command to the command handler. `command` is its name, and `callback` is the

View File

@ -20,7 +20,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/base64_unpadded.dart';
import 'crypto.dart'; import 'package:matrix/src/utils/crypto/crypto.dart';
class EncryptedFile { class EncryptedFile {
EncryptedFile({ EncryptedFile({

View File

@ -3,8 +3,8 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'subtle.dart' as subtle; import 'package:matrix/src/utils/crypto/subtle.dart' as subtle;
import 'subtle.dart'; import 'package:matrix/src/utils/crypto/subtle.dart';
abstract class Hash { abstract class Hash {
Hash._(this.name); Hash._(this.name);

View File

@ -4,7 +4,7 @@ import 'dart:typed_data';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'ffi.dart'; import 'package:matrix/src/utils/crypto/ffi.dart';
abstract class Hash { abstract class Hash {
Hash._(this.ptr); Hash._(this.ptr);

View File

@ -22,8 +22,8 @@ import 'package:canonical_json/canonical_json.dart';
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:olm/olm.dart' as olm; import 'package:olm/olm.dart' as olm;
import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import '../../encryption.dart';
enum UserVerifiedStatus { verified, unknown, unknownDevice } enum UserVerifiedStatus { verified, unknown, unknownDevice }
@ -349,6 +349,9 @@ abstract class SignableKey extends MatrixSignableKey {
bool operator ==(dynamic other) => (other is SignableKey && bool operator ==(dynamic other) => (other is SignableKey &&
other.userId == userId && other.userId == userId &&
other.identifier == identifier); other.identifier == identifier);
@override
int get hashCode => Object.hash(userId, identifier);
} }
class CrossSigningKey extends SignableKey { class CrossSigningKey extends SignableKey {

View File

@ -18,8 +18,8 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import '../../encryption.dart'; import 'package:matrix/encryption.dart';
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
abstract class EventLocalizations { abstract class EventLocalizations {
// As we need to create the localized body off of a different set of parameters, we // As we need to create the localized body off of a different set of parameters, we
@ -54,16 +54,16 @@ abstract class EventLocalizations {
String errorText; String errorText;
switch (event.body) { switch (event.body) {
case DecryptException.channelCorrupted: case DecryptException.channelCorrupted:
errorText = i18n.channelCorruptedDecryptError + '.'; errorText = '${i18n.channelCorruptedDecryptError}.';
break; break;
case DecryptException.notEnabled: case DecryptException.notEnabled:
errorText = i18n.encryptionNotEnabled + '.'; errorText = '${i18n.encryptionNotEnabled}.';
break; break;
case DecryptException.unknownAlgorithm: case DecryptException.unknownAlgorithm:
errorText = i18n.unknownEncryptionAlgorithm + '.'; errorText = '${i18n.unknownEncryptionAlgorithm}.';
break; break;
case DecryptException.unknownSession: case DecryptException.unknownSession:
errorText = i18n.noPermission + '.'; errorText = '${i18n.noPermission}.';
break; break;
default: default:
errorText = body; errorText = body;
@ -240,7 +240,7 @@ abstract class EventLocalizations {
var localizedBody = i18n.activatedEndToEndEncryption( var localizedBody = i18n.activatedEndToEndEncryption(
event.senderFromMemoryOrFallback.calcDisplayname()); event.senderFromMemoryOrFallback.calcDisplayname());
if (event.room.client.encryptionEnabled == false) { if (event.room.client.encryptionEnabled == false) {
localizedBody += '. ' + i18n.needPantalaimonWarning; localizedBody += '. ${i18n.needPantalaimonWarning}';
} }
return localizedBody; return localizedBody;
}, },

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
enum EventUpdateType { enum EventUpdateType {
timeline, timeline,

View File

@ -32,7 +32,7 @@ class HtmlToText {
// miss-matching tags, and this way we actually correctly identify what we want to strip and, well, // miss-matching tags, and this way we actually correctly identify what we want to strip and, well,
// strip it. // strip it.
final renderHtml = html.replaceAll( final renderHtml = html.replaceAll(
RegExp('<mx-reply>.*<\/mx-reply>', RegExp('<mx-reply>.*</mx-reply>',
caseSensitive: false, multiLine: false, dotAll: true), caseSensitive: false, multiLine: false, dotAll: true),
''); '');
@ -84,7 +84,7 @@ class HtmlToText {
static String _parseBlockquoteContent(_ConvertOpts opts, Element node) { static String _parseBlockquoteContent(_ConvertOpts opts, Element node) {
final msg = _walkChildNodes(opts, node); final msg = _walkChildNodes(opts, node);
return msg.split('\n').map((s) => '> $s').join('\n') + '\n'; return '${msg.split('\n').map((s) => '> $s').join('\n')}\n';
} }
static String _parseSpanContent(_ConvertOpts opts, Element node) { static String _parseSpanContent(_ConvertOpts opts, Element node) {
@ -109,10 +109,7 @@ class HtmlToText {
return entries return entries
.map((s) => .map((s) =>
(' ' * opts.listDepth) + '${' ' * opts.listDepth}$bulletPoint ${s.replaceAll('\n', '\n${' ' * opts.listDepth} ')}')
bulletPoint +
' ' +
s.replaceAll('\n', '\n' + (' ' * opts.listDepth) + ' '))
.join('\n'); .join('\n');
} }
@ -128,9 +125,7 @@ class HtmlToText {
return entries return entries
.mapIndexed((index, s) => .mapIndexed((index, s) =>
(' ' * opts.listDepth) + '${' ' * opts.listDepth}${start + index}. ${s.replaceAll('\n', '\n${' ' * opts.listDepth} ')}')
'${start + index}. ' +
s.replaceAll('\n', '\n' + (' ' * opts.listDepth) + ' '))
.join('\n'); .join('\n');
} }

View File

@ -20,7 +20,7 @@ import 'dart:async';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
/// Stream.timeout fails if no progress is made in timeLimit. /// Stream.timeout fails if no progress is made in timeLimit.
/// In contrast, streamTotalTimeout fails if the stream isn't completed /// In contrast, streamTotalTimeout fails if the stream isn't completed
@ -68,10 +68,6 @@ class FixedTimeoutHttpClient extends TimeoutHttpClient {
FixedTimeoutHttpClient(http.Client inner, this.timeout) : super(inner); FixedTimeoutHttpClient(http.Client inner, this.timeout) : super(inner);
@override @override
Duration timeout; Duration timeout;
@override
Future<http.StreamedResponse> send(http.BaseRequest request) =>
super.send(request);
} }
class VariableTimeoutHttpClient extends TimeoutHttpClient { class VariableTimeoutHttpClient extends TimeoutHttpClient {

View File

@ -19,14 +19,15 @@
import 'package:matrix_api_lite/matrix_api_lite.dart'; import 'package:matrix_api_lite/matrix_api_lite.dart';
import 'package:slugify/slugify.dart'; import 'package:slugify/slugify.dart';
import '../room.dart'; import 'package:matrix/src/room.dart';
extension ImagePackRoomExtension on Room { extension ImagePackRoomExtension on Room {
/// Get all the active image packs for the specified [usage], mapped by their slug /// Get all the active image packs for the specified [usage], mapped by their slug
Map<String, ImagePackContent> getImagePacks([ImagePackUsage? usage]) { Map<String, ImagePackContent> getImagePacks([ImagePackUsage? usage]) {
final allMxcs = <Uri>{}; // used for easy deduplication final allMxcs = <Uri>{}; // used for easy deduplication
final packs = <String, ImagePackContent>{}; final packs = <String, ImagePackContent>{};
final addImagePack = (BasicEvent? event, {Room? room, String? slug}) {
void addImagePack(BasicEvent? event, {Room? room, String? slug}) {
if (event == null) return; if (event == null) return;
final imagePack = event.parsedImagePackContent; final imagePack = event.parsedImagePackContent;
final finalSlug = slugify(slug ?? 'pack'); final finalSlug = slugify(slug ?? 'pack');
@ -53,7 +54,8 @@ extension ImagePackRoomExtension on Room {
.images[entry.key] = image; .images[entry.key] = image;
allMxcs.add(image.url); allMxcs.add(image.url);
} }
}; }
// first we add the user image pack // first we add the user image pack
addImagePack(client.accountData['im.ponies.user_emotes'], slug: 'user'); addImagePack(client.accountData['im.ponies.user_emotes'], slug: 'user');
// next we add all the external image packs // next we add all the external image packs

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import 'package:matrix_api_lite/src/utils/try_get_map_extension.dart'; import 'package:matrix_api_lite/matrix_api_lite.dart';
mixin EventType { mixin EventType {
static const String markedUnread = 'com.famedly.marked_unread'; static const String markedUnread = 'com.famedly.marked_unread';

View File

@ -25,8 +25,8 @@ import 'package:blurhash_dart/blurhash_dart.dart';
import 'package:image/image.dart'; import 'package:image/image.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
import 'compute_callback.dart'; import 'package:matrix/src/utils/compute_callback.dart';
class MatrixFile { class MatrixFile {
final Uint8List bytes; final Uint8List bytes;

View File

@ -81,14 +81,8 @@ extension MatrixIdExtension on String {
} }
return uri.replace(pathSegments: identifiers); return uri.replace(pathSegments: identifiers);
} else if (toLowerCase().startsWith(matrixToPrefix)) { } else if (toLowerCase().startsWith(matrixToPrefix)) {
return Uri.tryParse('//' + return Uri.tryParse(
substring(matrixToPrefix.length - 1) '//${substring(matrixToPrefix.length - 1).replaceAllMapped(RegExp(r'(?<=/)[#!@+][^:]*:|(\?.*$)'), (m) => m[0]!.replaceAllMapped(RegExp(m.group(1) != null ? '' : '[/?]'), (m) => Uri.encodeComponent(m.group(0)!))).replaceAll('#', '%23')}');
.replaceAllMapped(
RegExp(r'(?<=/)[#!@+][^:]*:|(\?.*$)'),
(m) => m[0]!.replaceAllMapped(
RegExp(m.group(1) != null ? '' : '[/?]'),
(m) => Uri.encodeComponent(m.group(0)!)))
.replaceAll('#', '%23'));
} else { } else {
return Uri( return Uri(
pathSegments: RegExp(r'/((?:[#!@+][^:]*:)?[^/?]*)(?:\?.*$)?') pathSegments: RegExp(r'/((?:[#!@+][^:]*:)?[^/?]*)(?:\?.*$)?')

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../room.dart'; import 'package:matrix/src/room.dart';
abstract class MatrixLocalizations { abstract class MatrixLocalizations {
const MatrixLocalizations(); const MatrixLocalizations();

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'compute_callback.dart'; import 'package:matrix/src/utils/compute_callback.dart';
/// provides native implementations for demanding arithmetic operations /// provides native implementations for demanding arithmetic operations
/// in order to prevent the UI from blocking /// in order to prevent the UI from blocking

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../user.dart'; import 'package:matrix/src/user.dart';
/// Represents a receipt. /// Represents a receipt.
/// This [user] has read an event at the given [time]. /// This [user] has read an event at the given [time].
@ -30,4 +30,7 @@ class Receipt {
bool operator ==(dynamic other) => (other is Receipt && bool operator ==(dynamic other) => (other is Receipt &&
other.user == user && other.user == user &&
other.time.microsecondsSinceEpoch == time.microsecondsSinceEpoch); other.time.microsecondsSinceEpoch == time.microsecondsSinceEpoch);
@override
int get hashCode => Object.hash(user, time);
} }

View File

@ -18,7 +18,7 @@
import 'dart:async'; import 'dart:async';
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
Future<T?> runInRoot<T>(FutureOr<T> Function() fn) async { Future<T?> runInRoot<T>(FutureOr<T> Function() fn) async {
return await Zone.root.run(() async { return await Zone.root.run(() async {

View File

@ -18,7 +18,7 @@
import 'package:matrix_api_lite/matrix_api_lite.dart'; import 'package:matrix_api_lite/matrix_api_lite.dart';
import '../event.dart'; import 'package:matrix/src/event.dart';
class SpaceChild { class SpaceChild {
final String? roomId; final String? roomId;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
/// This extension adds easy-to-use filters for the sync update, meant to be used on the `client.onSync` stream, e.g. /// This extension adds easy-to-use filters for the sync update, meant to be used on the `client.onSync` stream, e.g.
/// `client.onSync.stream.where((s) => s.hasRoomUpdate)`. Multiple filters can easily be /// `client.onSync.stream.where((s) => s.hasRoomUpdate)`. Multiple filters can easily be

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
class ToDeviceEvent extends BasicEventWithSender { class ToDeviceEvent extends BasicEventWithSender {
Map<String, dynamic>? encryptedContent; Map<String, dynamic>? encryptedContent;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import '../../matrix.dart'; import 'package:matrix/matrix.dart';
enum UiaRequestState { enum UiaRequestState {
/// The request is done /// The request is done

View File

@ -18,7 +18,7 @@
import 'dart:core'; import 'dart:core';
import '../client.dart'; import 'package:matrix/src/client.dart';
extension MxcUriExtension on Uri { extension MxcUriExtension on Uri {
/// Returns a download Link to this content. /// Returns a download Link to this content.

View File

@ -11,7 +11,7 @@ import 'package:js/js_util.dart';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.dart' hide Event; import 'package:matrix/matrix.dart' hide Event;
import 'native_implementations_web_worker.dart'; import 'package:matrix/src/utils/web_worker/native_implementations_web_worker.dart';
/// ///
/// ///

View File

@ -21,8 +21,8 @@ import 'dart:core';
import 'package:webrtc_interface/webrtc_interface.dart'; import 'package:webrtc_interface/webrtc_interface.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import '../../matrix.dart';
/// https://github.com/matrix-org/matrix-doc/pull/2746 /// https://github.com/matrix-org/matrix-doc/pull/2746
/// version 1 /// version 1
@ -997,7 +997,7 @@ class CallSession {
} }
Future<void> onRejectReceived(String? reason) async { Future<void> onRejectReceived(String? reason) async {
Logs().v('[VOIP] Reject received for call ID ' + callId); Logs().v('[VOIP] Reject received for call ID $callId');
// No need to check party_id for reject because if we'd received either // No need to check party_id for reject because if we'd received either
// an answer or reject, we wouldn't be in state InviteSent // an answer or reject, we wouldn't be in state InviteSent
final shouldTerminate = (state == CallState.kFledgling && final shouldTerminate = (state == CallState.kFledgling &&

View File

@ -28,5 +28,5 @@ String roomAliasFromRoomName(String roomName) {
} }
String genCallID() { String genCallID() {
return '${DateTime.now().millisecondsSinceEpoch}' + randomAlphaNumeric(16); return '${DateTime.now().millisecondsSinceEpoch}${randomAlphaNumeric(16)}';
} }

View File

@ -4,8 +4,8 @@ import 'dart:core';
import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform;
import 'package:webrtc_interface/webrtc_interface.dart'; import 'package:webrtc_interface/webrtc_interface.dart';
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import '../../matrix.dart';
/// Delegate WebRTC basic functionality. /// Delegate WebRTC basic functionality.
abstract class WebRTCDelegate { abstract class WebRTCDelegate {
@ -317,7 +317,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('Reject received for call ID ' + callId); Logs().d('Reject received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
if (call != null) { if (call != null) {
@ -339,7 +339,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('onCallReplaces received for call ID ' + callId); Logs().d('onCallReplaces received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
if (call != null) { if (call != null) {
if (call.room.id != roomId) { if (call.room.id != roomId) {
@ -358,7 +358,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('SelectAnswer received for call ID ' + callId); Logs().d('SelectAnswer received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
final String selectedPartyId = content['selected_party_id']; final String selectedPartyId = content['selected_party_id'];
@ -379,7 +379,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('SDP Stream metadata received for call ID ' + callId); Logs().d('SDP Stream metadata received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
if (call != null) { if (call != null) {
if (call.room.id != roomId) { if (call.room.id != roomId) {
@ -404,7 +404,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('Asserted identity received for call ID ' + callId); Logs().d('Asserted identity received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
if (call != null) { if (call != null) {
if (call.room.id != roomId) { if (call.room.id != roomId) {
@ -429,7 +429,7 @@ class VoIP {
return; return;
} }
final String callId = content['call_id']; final String callId = content['call_id'];
Logs().d('Negotiate received for call ID ' + callId); Logs().d('Negotiate received for call ID $callId');
final call = calls[callId]; final call = calls[callId];
if (call != null) { if (call != null) {
if (call.room.id != roomId) { if (call.room.id != roomId) {

View File

@ -9,33 +9,33 @@ environment:
dependencies: dependencies:
async: ^2.8.0 async: ^2.8.0
blurhash_dart: ^1.1.0
http: ^0.13.0
mime: ^1.0.0
canonical_json: ^1.1.0
markdown: ^4.0.0
html_unescape: ^2.0.0
random_string: ^2.3.1
crypto: ^3.0.0
base58check: ^2.0.0 base58check: ^2.0.0
olm: ^2.0.0 blurhash_dart: ^1.1.0
matrix_api_lite: ^1.1.0 canonical_json: ^1.1.0
hive: ^2.2.1
image: ^3.1.1
ffi: ^1.0.0
js: ^0.6.3
slugify: ^2.0.0
html: ^0.15.0
collection: ^1.15.0 collection: ^1.15.0
webrtc_interface: ^1.0.1 crypto: ^3.0.0
sdp_transform: ^0.3.2 ffi: ^1.0.0
fluffybox: ^0.4.3 fluffybox: ^0.4.3
hive: ^2.2.1
html: ^0.15.0
html_unescape: ^2.0.0
http: ^0.13.0
image: ^3.1.1
js: ^0.6.3
markdown: ^4.0.0
matrix_api_lite: ^1.1.0
mime: ^1.0.0
olm: ^2.0.0
random_string: ^2.3.1
sdp_transform: ^0.3.2
slugify: ^2.0.0
webrtc_interface: ^1.0.1
dev_dependencies: dev_dependencies:
import_sorter: ^4.6.0
dart_code_metrics: ^4.10.1
pedantic: ^1.11.0
test: ^1.15.7
coverage: ">=0.15.0 <2.0.0" coverage: ">=0.15.0 <2.0.0"
dart_code_metrics: ^4.10.1
file: ^6.1.1 file: ^6.1.1
import_sorter: ^4.6.0
lints: ^2.0.0
test: ^1.15.7
#flutter_test: {sdk: flutter} #flutter_test: {sdk: flutter}

File diff suppressed because one or more lines are too long

View File

@ -31,15 +31,15 @@ void main() {
late Room room; late Room room;
var olmEnabled = true; var olmEnabled = true;
final getLastMessagePayload = Map<String, dynamic> getLastMessagePayload(
([String type = 'm.room.message', String? stateKey]) { [String type = 'm.room.message', String? stateKey]) {
final state = stateKey != null; final state = stateKey != null;
return json.decode(FakeMatrixApi.calledEndpoints.entries return json.decode(FakeMatrixApi.calledEndpoints.entries
.firstWhere((e) => e.key.startsWith( .firstWhere((e) => e.key.startsWith(
'/client/v3/rooms/${Uri.encodeComponent(room.id)}/${state ? 'state' : 'send'}/${Uri.encodeComponent(type)}${state && stateKey?.isNotEmpty == true ? '/' + Uri.encodeComponent(stateKey!) : ''}')) '/client/v3/rooms/${Uri.encodeComponent(room.id)}/${state ? 'state' : 'send'}/${Uri.encodeComponent(type)}${state && stateKey?.isNotEmpty == true ? '/${Uri.encodeComponent(stateKey!)}' : ''}'))
.value .value
.first); .first);
}; }
test('setupClient', () async { test('setupClient', () async {
try { try {
@ -57,7 +57,7 @@ void main() {
stateKey: '', stateKey: '',
eventId: '\$fakeeventid', eventId: '\$fakeeventid',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
)); ));
room.setState(Event( room.setState(Event(
type: 'm.room.member', type: 'm.room.member',
@ -66,7 +66,7 @@ void main() {
stateKey: client.userID, stateKey: client.userID,
eventId: '\$fakeeventid', eventId: '\$fakeeventid',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
)); ));
}); });

View File

@ -128,7 +128,7 @@ void main() {
var decoded = SSSS.decodeRecoveryKey(encoded); var decoded = SSSS.decodeRecoveryKey(encoded);
expect(key, decoded); expect(key, decoded);
decoded = SSSS.decodeRecoveryKey(encoded + ' \n\t'); decoded = SSSS.decodeRecoveryKey('$encoded \n\t');
expect(key, decoded); expect(key, decoded);
final handle = client.encryption!.ssss.open(); final handle = client.encryption!.ssss.open();

View File

@ -1043,7 +1043,7 @@ void main() {
'# Title\nsome text and [link](https://example.com)\nokay and this is **important**', '# Title\nsome text and [link](https://example.com)\nokay and this is **important**',
'format': 'org.matrix.custom.html', 'format': 'org.matrix.custom.html',
'formatted_body': 'formatted_body':
'<h1>Title</h1>\n<p>some text and <a href=\"https://example.com\">link</a><br>okay and this is <strong>important</strong></p>\n', '<h1>Title</h1>\n<p>some text and <a href="https://example.com">link</a><br>okay and this is <strong>important</strong></p>\n',
'msgtype': 'm.text' 'msgtype': 'm.text'
}, },
'event_id': '\$143273582443PhrSn:example.org', 'event_id': '\$143273582443PhrSn:example.org',
@ -1253,12 +1253,13 @@ void main() {
test('attachments', () async { test('attachments', () async {
final FILE_BUFF = Uint8List.fromList([0]); final FILE_BUFF = Uint8List.fromList([0]);
final THUMBNAIL_BUFF = Uint8List.fromList([2]); final THUMBNAIL_BUFF = Uint8List.fromList([2]);
final downloadCallback = (Uri uri) async { Future<Uint8List> downloadCallback(Uri uri) async {
return { return {
'/_matrix/media/v3/download/example.org/file': FILE_BUFF, '/_matrix/media/v3/download/example.org/file': FILE_BUFF,
'/_matrix/media/v3/download/example.org/thumb': THUMBNAIL_BUFF, '/_matrix/media/v3/download/example.org/thumb': THUMBNAIL_BUFF,
}[uri.path]!; }[uri.path]!;
}; }
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'), await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
checkWellKnown: false); checkWellKnown: false);
final room = Room(id: '!localpart:server.abc', client: client); final room = Room(id: '!localpart:server.abc', client: client);
@ -1344,12 +1345,13 @@ void main() {
Uint8List.fromList([0x55, 0xD7, 0xEB, 0x72, 0x05, 0x13]); Uint8List.fromList([0x55, 0xD7, 0xEB, 0x72, 0x05, 0x13]);
final THUMB_BUFF_DEC = final THUMB_BUFF_DEC =
Uint8List.fromList([0x74, 0x68, 0x75, 0x6D, 0x62, 0x0A]); Uint8List.fromList([0x74, 0x68, 0x75, 0x6D, 0x62, 0x0A]);
final downloadCallback = (Uri uri) async { Future<Uint8List> downloadCallback(Uri uri) async {
return { return {
'/_matrix/media/v3/download/example.com/file': FILE_BUFF_ENC, '/_matrix/media/v3/download/example.com/file': FILE_BUFF_ENC,
'/_matrix/media/v3/download/example.com/thumb': THUMB_BUFF_ENC, '/_matrix/media/v3/download/example.com/thumb': THUMB_BUFF_ENC,
}[uri.path]!; }[uri.path]!;
}; }
final room = Room(id: '!localpart:server.abc', client: await getClient()); final room = Room(id: '!localpart:server.abc', client: await getClient());
var event = Event.fromJson({ var event = Event.fromJson({
'type': EventTypes.Message, 'type': EventTypes.Message,
@ -1440,12 +1442,13 @@ void main() {
test('downloadAndDecryptAttachment store', () async { test('downloadAndDecryptAttachment store', () async {
final FILE_BUFF = Uint8List.fromList([0]); final FILE_BUFF = Uint8List.fromList([0]);
var serverHits = 0; var serverHits = 0;
final downloadCallback = (Uri uri) async { Future<Uint8List> downloadCallback(Uri uri) async {
serverHits++; serverHits++;
return { return {
'/_matrix/media/v3/download/example.org/newfile': FILE_BUFF, '/_matrix/media/v3/download/example.org/newfile': FILE_BUFF,
}[uri.path]!; }[uri.path]!;
}; }
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'), await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
checkWellKnown: false); checkWellKnown: false);
final room = Room(id: '!localpart:server.abc', client: await getClient()); final room = Room(id: '!localpart:server.abc', client: await getClient());
@ -1606,7 +1609,7 @@ void main() {
''', ''',
'format': 'org.matrix.custom.html', 'format': 'org.matrix.custom.html',
'formatted_body': 'formatted_body':
'\<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>😒😒</blockquote></mx-reply>❤❤❤' '<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>😒😒</blockquote></mx-reply>❤❤❤'
}, },
'event_id': '\$edit2', 'event_id': '\$edit2',
'sender': '@alice:example.org', 'sender': '@alice:example.org',
@ -1622,7 +1625,7 @@ void main() {
''', ''',
'format': 'org.matrix.custom.html', 'format': 'org.matrix.custom.html',
'formatted_body': 'formatted_body':
'\<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>A 😒</blockquote></mx-reply>❤❤' '<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>A 😒</blockquote></mx-reply>❤❤'
}, },
'event_id': '\$edit2', 'event_id': '\$edit2',
'sender': '@alice:example.org', 'sender': '@alice:example.org',
@ -1638,7 +1641,7 @@ void main() {
A''', A''',
'format': 'org.matrix.custom.html', 'format': 'org.matrix.custom.html',
'formatted_body': 'formatted_body':
'\<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>😒😒😒</blockquote></mx-reply>❤A❤' '<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>😒😒😒</blockquote></mx-reply>❤A❤'
}, },
'event_id': '\$edit2', 'event_id': '\$edit2',
'sender': '@alice:example.org', 'sender': '@alice:example.org',
@ -1654,7 +1657,7 @@ void main() {
A''', A''',
'format': 'org.matrix.custom.html', 'format': 'org.matrix.custom.html',
'formatted_body': 'formatted_body':
'\<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>A😒</blockquote></mx-reply>❤A❤' '<mx-reply><blockquote><a href="https://fakeserver.notexisting/\$jEsUZKDJdhlrceRyVU">In reply to</a> <a href="https://fakeserver.notexisting/@alice:example.org">@alice:example.org</a><br>A😒</blockquote></mx-reply>❤A❤'
}, },
'event_id': '\$edit2', 'event_id': '\$edit2',
'sender': '@alice:example.org', 'sender': '@alice:example.org',

View File

@ -92,7 +92,7 @@ class FakeMatrixApi extends BaseClient {
var action = request.url.path; var action = request.url.path;
if (request.url.path.contains('/_matrix')) { if (request.url.path.contains('/_matrix')) {
action = action =
request.url.path.split('/_matrix').last + '?' + request.url.query; '${request.url.path.split('/_matrix').last}?${request.url.query}';
} }
// ignore: avoid_print // ignore: avoid_print

View File

@ -45,7 +45,7 @@ void main() {
content: {'membership': 'join'}, content: {'membership': 'join'},
room: room, room: room,
stateKey: client.userID, stateKey: client.userID,
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid2:fakeServer.notExisting', eventId: '\$fakeid2:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -63,7 +63,7 @@ void main() {
content: {'membership': 'join'}, content: {'membership': 'join'},
room: room, room: room,
stateKey: client.userID, stateKey: client.userID,
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid4:fakeServer.notExisting', eventId: '\$fakeid4:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -81,7 +81,7 @@ void main() {
}, },
room: room, room: room,
stateKey: '', stateKey: '',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid5:fakeServer.notExisting', eventId: '\$fakeid5:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -110,7 +110,7 @@ void main() {
}, },
room: room, room: room,
stateKey: '', stateKey: '',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid6:fakeServer.notExisting', eventId: '\$fakeid6:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -135,7 +135,7 @@ void main() {
}, },
room: room, room: room,
stateKey: '', stateKey: '',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid7:fakeServer.notExisting', eventId: '\$fakeid7:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -158,7 +158,7 @@ void main() {
}, },
room: room, room: room,
stateKey: 'fox', stateKey: 'fox',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid8:fakeServer.notExisting', eventId: '\$fakeid8:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -201,7 +201,7 @@ void main() {
}, },
room: room2, room: room2,
stateKey: '', stateKey: '',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid9:fakeServer.notExisting', eventId: '\$fakeid9:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));
@ -234,7 +234,7 @@ void main() {
}, },
room: room2, room: room2,
stateKey: 'fox', stateKey: 'fox',
senderId: '\@fakeuser:fakeServer.notExisting', senderId: '@fakeuser:fakeServer.notExisting',
eventId: '\$fakeid10:fakeServer.notExisting', eventId: '\$fakeid10:fakeServer.notExisting',
originServerTs: DateTime.now(), originServerTs: DateTime.now(),
)); ));

View File

@ -40,7 +40,7 @@ void main() {
'@[Fast Fox]#123': '@fastfox:example.org', '@[Fast Fox]#123': '@fastfox:example.org',
'@[">]': '@blah:example.org', '@[">]': '@blah:example.org',
}; };
final getMention = (mention) => mentionMap[mention]; String? getMention(mention) => mentionMap[mention];
test('simple markdown', () { test('simple markdown', () {
expect(markdown('hey *there* how are **you** doing?'), expect(markdown('hey *there* how are **you** doing?'),
'hey <em>there</em> how are <strong>you</strong> doing?'); 'hey <em>there</em> how are <strong>you</strong> doing?');

View File

@ -139,7 +139,7 @@ void main() {
group('Sync Filters', () { group('Sync Filters', () {
Logs().level = Level.error; Logs().level = Level.error;
test('room update', () { test('room update', () {
final testFn = (SyncUpdate s) => s.hasRoomUpdate; bool testFn(SyncUpdate s) => s.hasRoomUpdate;
final expected = { final expected = {
'empty': false, 'empty': false,
'presence': false, 'presence': false,
@ -153,7 +153,7 @@ void main() {
}); });
test('presence update', () { test('presence update', () {
final testFn = (SyncUpdate s) => s.hasPresenceUpdate; bool testFn(SyncUpdate s) => s.hasPresenceUpdate;
final expected = { final expected = {
'empty': false, 'empty': false,
'presence': true, 'presence': true,