chore: matrix_api_lite 0.4.0

This commit is contained in:
Lukas Lihotzki 2021-06-07 17:18:23 +02:00
parent bc2dac2ecc
commit 8665f092f4
17 changed files with 185 additions and 166 deletions

View File

@ -44,7 +44,8 @@ class KeyManager {
final keyObj = olm.PkDecryption();
try {
final info = await getRoomKeysBackupInfo(false);
if (info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2) {
if (info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2) {
return false;
}
return keyObj.init_with_private_key(base64.decode(secret)) ==
@ -523,9 +524,9 @@ class KeyManager {
return (await encryption.ssss.getCached(megolmKey)) != null;
}
RoomKeysVersionResponse _roomKeysVersionCache;
GetRoomKeysVersionCurrentResponse _roomKeysVersionCache;
DateTime _roomKeysVersionCacheDate;
Future<RoomKeysVersionResponse> getRoomKeysBackupInfo(
Future<GetRoomKeysVersionCurrentResponse> getRoomKeysBackupInfo(
[bool useCache = true]) async {
if (_roomKeysVersionCache != null &&
_roomKeysVersionCacheDate != null &&
@ -535,7 +536,7 @@ class KeyManager {
.isBefore(_roomKeysVersionCacheDate)) {
return _roomKeysVersionCache;
}
_roomKeysVersionCache = await client.getRoomKeysBackup();
_roomKeysVersionCache = await client.getRoomKeysVersionCurrent();
_roomKeysVersionCacheDate = DateTime.now();
return _roomKeysVersionCache;
}
@ -553,7 +554,7 @@ class KeyManager {
backupPubKey = decryption.init_with_private_key(privateKey);
if (backupPubKey == null ||
info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 ||
info.algorithm != BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
@ -706,7 +707,8 @@ class KeyManager {
backupPubKey = decryption.init_with_private_key(privateKey);
if (backupPubKey == null ||
info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2 ||
info.algorithm !=
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2 ||
info.authData['public_key'] != backupPubKey) {
return;
}
@ -737,7 +739,7 @@ class KeyManager {
_generateUploadKeys, args);
Logs().i('[Key Manager] Uploading ${dbSessions.length} room keys...');
// upload the payload...
await client.storeRoomKeys(info.version, roomKeys);
await client.postRoomKeysKey(info.version, roomKeys);
// and now finally mark all the keys as uploaded
// no need to optimze this, as we only run it so seldomly and almost never with many keys at once
for (final dbSession in dbSessions) {
@ -1011,7 +1013,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
try {
enc.set_recipient_key(args.pubkey);
// first we generate the payload to upload all the session keys in this chunk
final roomKeys = RoomKeys();
final roomKeys = RoomKeys(rooms: {});
for (final dbSession in args.dbSessions) {
final sess = SessionKey.fromDb(dbSession.dbSession, args.userId);
if (!sess.isValid) {
@ -1019,7 +1021,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
}
// create the room if it doesn't exist
if (!roomKeys.rooms.containsKey(sess.roomId)) {
roomKeys.rooms[sess.roomId] = RoomKeysRoom();
roomKeys.rooms[sess.roomId] = RoomKeyBackup(sessions: {});
}
// generate the encrypted content
final payload = <String, dynamic>{
@ -1035,7 +1037,7 @@ RoomKeys _generateUploadKeys(_GenerateUploadKeysArgs args) {
// fetch the device, if available...
//final device = args.client.getUserDeviceKeysByCurve25519Key(sess.senderKey);
// aaaand finally add the session key to our payload
roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = RoomKeysSingleKey(
roomKeys.rooms[sess.roomId].sessions[sess.sessionId] = KeyBackupData(
firstMessageIndex: sess.inboundGroupSession.first_known_index(),
forwardedCount: sess.forwardingCurve25519KeyChain.length,
isVerified: dbSession.verified, //device?.verified ?? false,

View File

@ -23,9 +23,9 @@ import 'package:matrix/matrix.dart';
import 'package:olm/olm.dart' as olm;
import '../encryption/utils/json_signature_check_extension.dart';
import '../src/utils/run_in_root.dart';
import 'encryption.dart';
import 'utils/olm_session.dart';
import '../src/utils/run_in_root.dart';
class OlmManager {
final Encryption encryption;
@ -264,7 +264,7 @@ class OlmManager {
// and generate and upload more if not.
// If the server did not send us a count, assume it is 0
final keyCount = countJson?.tryGet<int>('signed_curve25519', 0) ?? 0;
final keyCount = countJson?.tryGet<int>('signed_curve25519') ?? 0;
// If the server does not support fallback keys, it will not tell us about them.
// If the server supports them but has no key, upload a new one.
@ -644,7 +644,7 @@ class OlmManager {
return;
}
final device = client.getUserDeviceKeysByCurve25519Key(
event.encryptedContent.tryGet<String>('sender_key', ''));
event.encryptedContent.tryGet<String>('sender_key') ?? '');
if (device == null) {
return; // device not found
}

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:core';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'dart:typed_data';
import 'package:base58check/base58.dart';
import 'package:crypto/crypto.dart';
@ -51,11 +51,13 @@ const pbkdf2SaltLength = 64;
/// https://matrix.org/docs/guides/implementing-more-advanced-e-2-ee-features-such-as-cross-signing#3-implementing-ssss
class SSSS {
final Encryption encryption;
Client get client => encryption.client;
final pendingShareRequests = <String, _ShareRequest>{};
final _validators = <String, FutureOr<bool> Function(String)>{};
final _cacheCallbacks = <String, FutureOr<void> Function(String)>{};
final Map<String, SSSSCache> _cache = <String, SSSSCache>{};
SSSS(this.encryption);
// for testing
@ -174,7 +176,7 @@ class SSSS {
await client.setAccountData(
client.userID,
EventTypes.SecretStorageDefaultKey,
(SecretStorageDefaultKeyContent()..key = keyId).toJson(),
SecretStorageDefaultKeyContent(key: keyId).toJson(),
);
}
@ -193,13 +195,12 @@ class SSSS {
final content = SecretStorageKeyContent();
if (passphrase != null) {
// we need to derive the key off of the passphrase
content.passphrase = PassphraseInfo();
content.passphrase.algorithm = AlgorithmTypes.pbkdf2;
content.passphrase.salt = base64
.encode(uc.secureRandomBytes(pbkdf2SaltLength)); // generate salt
content.passphrase.iterations = pbkdf2DefaultIterations;
;
content.passphrase.bits = ssssKeyLength * 8;
content.passphrase = PassphraseInfo(
iterations: pbkdf2DefaultIterations,
salt: base64.encode(uc.secureRandomBytes(pbkdf2SaltLength)),
algorithm: AlgorithmTypes.pbkdf2,
bits: ssssKeyLength * 8,
);
privateKey = await client
.runInBackground(
_keyFromPassphrase,
@ -431,6 +432,7 @@ class SSSS {
DateTime _lastCacheRequest;
bool _isPeriodicallyRequestingMissingCache = false;
Future<void> periodicallyRequestMissingCache() async {
if (_isPeriodicallyRequestingMissingCache ||
(_lastCacheRequest != null &&
@ -606,10 +608,13 @@ class OpenSSSS {
final SSSS ssss;
final String keyId;
final SecretStorageKeyContent keyData;
OpenSSSS({this.ssss, this.keyId, this.keyData});
Uint8List privateKey;
bool get isUnlocked => privateKey != null;
bool get hasPassphrase => keyData.passphrase != null;
String get recoveryKey =>
@ -708,6 +713,7 @@ class OpenSSSS {
class _KeyFromPassphraseArgs {
final String passphrase;
final PassphraseInfo info;
_KeyFromPassphraseArgs({this.passphrase, this.info});
}

View File

@ -563,8 +563,8 @@ class Bootstrap {
keyObj.free();
}
Logs().v('Create the new backup version...');
await client.createRoomKeysBackup(
RoomKeysAlgorithmType.v1Curve25519AesSha2,
await client.postRoomKeysVersion(
BackupAlgorithm.mMegolmBackupV1Curve25519AesSha2,
<String, dynamic>{
'public_key': pubKey,
},

View File

@ -21,8 +21,8 @@ import 'dart:convert';
import 'dart:core';
import 'dart:typed_data';
import 'package:matrix/src/utils/run_in_root.dart';
import 'package:http/http.dart' as http;
import 'package:matrix/src/utils/run_in_root.dart';
import 'package:olm/olm.dart' as olm;
import '../encryption.dart';
@ -44,6 +44,12 @@ typedef RoomSorter = int Function(Room a, Room b);
enum LoginState { logged, loggedOut }
extension TrailingSlash on Uri {
Uri stripTrailingSlash() => path.endsWith('/')
? replace(path: path.substring(0, path.length - 1))
: this;
}
/// Represents a Matrix client to communicate with a
/// [Matrix](https://matrix.org) homeserver and is the entry point for this
/// SDK.
@ -158,12 +164,13 @@ class Client extends MatrixApi {
this.compute,
Filter syncFilter,
@deprecated bool debug,
}) : syncFilter = syncFilter ??
}) : syncFilter = syncFilter ??
Filter(
room: RoomFilter(
state: StateFilter(lazyLoadMembers: true),
),
) {
),
super(httpClient: httpClient) {
supportedLoginTypes ??= {AuthenticationTypes.password};
verificationMethods ??= <KeyVerificationMethod>{};
importantStateEvents ??= {};
@ -182,7 +189,6 @@ class Client extends MatrixApi {
EventTypes.Encrypted,
EventTypes.Sticker,
]);
this.httpClient = httpClient ?? http.Client();
// register all the default commands
registerDefaultCommands();
@ -308,7 +314,7 @@ class Client extends MatrixApi {
}
/// Gets discovery information about the domain. The file may include additional keys.
Future<WellKnownInformation> getWellKnownInformationsByUserId(
Future<DiscoveryInformation> getDiscoveryInformationsByUserId(
String MatrixIdOrDomain,
) async {
try {
@ -321,15 +327,14 @@ class Client extends MatrixApi {
// No-OP
}
final rawJson = json.decode(respBody);
return WellKnownInformation.fromJson(rawJson);
return DiscoveryInformation.fromJson(rawJson);
} catch (_) {
// we got an error processing or fetching the well-known information, let's
// provide a reasonable fallback.
return WellKnownInformation.fromJson(<String, dynamic>{
'm.homeserver': <String, dynamic>{
'base_url': Uri.https(MatrixIdOrDomain.domain, '').toString(),
},
});
return DiscoveryInformation(
mHomeserver: HomeserverInformation(
baseUrl: Uri.https(MatrixIdOrDomain.domain, '')),
);
}
}
@ -347,35 +352,19 @@ class Client extends MatrixApi {
/// login types. Throws an exception if the server is not compatible with the
/// client and sets [homeserver] to [homeserverUrl] if it is. Supports the
/// types `Uri` and `String`.
Future<WellKnownInformation> checkHomeserver(dynamic homeserverUrl,
Future<DiscoveryInformation> checkHomeserver(dynamic homeserverUrl,
{bool checkWellKnown = true}) async {
try {
if (homeserverUrl is Uri) {
homeserver = homeserverUrl;
} else {
// URLs allow to have whitespace surrounding them, see https://www.w3.org/TR/2011/WD-html5-20110525/urls.html
// As we want to strip a trailing slash, though, we have to trim the url ourself
// and thus can't let Uri.parse() deal with it.
homeserverUrl = homeserverUrl.trim();
// strip a trailing slash
if (homeserverUrl.endsWith('/')) {
homeserverUrl = homeserverUrl.substring(0, homeserverUrl.length - 1);
}
homeserver = Uri.parse(homeserverUrl);
}
homeserver =
(homeserverUrl is Uri) ? homeserverUrl : Uri.parse(homeserverUrl);
homeserver = homeserver.stripTrailingSlash();
// Look up well known
WellKnownInformation wellKnown;
DiscoveryInformation wellKnown;
if (checkWellKnown) {
try {
wellKnown = await getWellknown();
homeserverUrl = wellKnown.mHomeserver.baseUrl.trim();
// strip a trailing slash
if (homeserverUrl.endsWith('/')) {
homeserverUrl =
homeserverUrl.substring(0, homeserverUrl.length - 1);
}
homeserver = Uri.parse(homeserverUrl);
homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash();
} catch (e) {
Logs().v('Found no well known information', e);
}
@ -390,9 +379,9 @@ class Client extends MatrixApi {
}
final loginTypes = await getLoginFlows();
if (!loginTypes.flows.any((f) => supportedLoginTypes.contains(f.type))) {
if (!loginTypes.any((f) => supportedLoginTypes.contains(f.type))) {
throw BadServerLoginTypesException(
loginTypes.flows.map((f) => f.type).toSet(), supportedLoginTypes);
loginTypes.map((f) => f.type).toSet(), supportedLoginTypes);
}
return wellKnown;
@ -406,14 +395,14 @@ class Client extends MatrixApi {
/// Returns the fully-qualified Matrix user ID (MXID) that has been registered.
/// You have to call [checkHomeserver] first to set a homeserver.
@override
Future<LoginResponse> register({
Future<RegisterResponse> register({
String username,
String password,
String deviceId,
String initialDeviceDisplayName,
bool inhibitLogin,
AuthenticationData auth,
String kind,
AccountKind kind,
}) async {
final response = await super.register(
username: username,
@ -447,8 +436,8 @@ class Client extends MatrixApi {
/// Maybe you want to set [user] to the same String to stay compatible with
/// older server versions.
@override
Future<LoginResponse> login({
String type = AuthenticationTypes.password,
Future<LoginResponse> login(
LoginType type, {
AuthenticationIdentifier identifier,
String password,
String token,
@ -463,13 +452,12 @@ class Client extends MatrixApi {
await checkHomeserver(user.domain);
}
final loginResp = await super.login(
type: type,
type,
identifier: identifier,
password: password,
token: token,
deviceId: deviceId,
initialDeviceDisplayName: initialDeviceDisplayName,
auth: auth,
// ignore: deprecated_member_use
user: user,
// ignore: deprecated_member_use
@ -553,7 +541,7 @@ class Client extends MatrixApi {
roomId = await createRoom(
invite: [mxid],
isDirect: true,
preset: CreateRoomPreset.trusted_private_chat,
preset: CreateRoomPreset.trustedPrivateChat,
);
if (roomId == null) return roomId;
@ -576,7 +564,7 @@ class Client extends MatrixApi {
Visibility visibility = Visibility.public,
String spaceAliasName,
List<String> invite,
List<Map<String, dynamic>> invite3pid,
List<Invite3pid> invite3pid,
String roomVersion,
}) =>
createRoom(
@ -607,7 +595,7 @@ class Client extends MatrixApi {
return getProfileFromUserId(userID);
}
final Map<String, Profile> _profileCache = {};
final Map<String, ProfileInformation> _profileCache = {};
/// Get the combined profile information for this user.
/// If [getFromRooms] is true then the profile will first be searched from the
@ -629,15 +617,25 @@ class Client extends MatrixApi {
if (room != null) {
final user =
room.getParticipants().firstWhere((User user) => user.id == userId);
return Profile(user.displayName, user.avatarUrl);
return Profile(
userId: userId,
displayName: user.displayName,
avatarUrl: user.avatarUrl);
}
}
if (cache && _profileCache.containsKey(userId)) {
return _profileCache[userId];
final profile = _profileCache[userId];
return Profile(
userId: userId,
displayName: profile.displayname,
avatarUrl: profile.avatarUrl);
}
final profile = await getUserProfile(userId);
_profileCache[userId] = profile;
return profile;
return Profile(
userId: userId,
displayName: profile.displayname,
avatarUrl: profile.avatarUrl);
}
Future<List<Room>> get archive async {
@ -685,10 +683,10 @@ class Client extends MatrixApi {
/// Uploads a file and automatically caches it in the database, if it is small enough
/// and returns the mxc url as a string.
@override
Future<String> uploadContent(Uint8List file, String fileName,
{String contentType}) async {
final mxc =
await super.uploadContent(file, fileName, contentType: contentType);
Future<String> uploadContent(Uint8List file,
{String filename, String contentType}) async {
final mxc = await super
.uploadContent(file, filename: filename, contentType: contentType);
final storeable = database != null && file.length <= database.maxFileSize;
if (storeable) {
await database.storeFile(
@ -715,7 +713,7 @@ class Client extends MatrixApi {
/// Uploads a new user avatar for this user.
Future<void> setAvatar(MatrixFile file) async {
final uploadResp = await uploadContent(file.bytes, file.name);
final uploadResp = await uploadContent(file.bytes, filename: file.name);
await setAvatarUrl(userID, Uri.parse(uploadResp));
return;
}
@ -2006,16 +2004,16 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
/// Changes the password. You should either set oldPasswort or another authentication flow.
@override
Future<void> changePassword(String newPassword,
{String oldPassword, AuthenticationData auth}) async {
{String oldPassword, AuthenticationData auth, bool logoutDevices}) async {
try {
if (oldPassword != null) {
auth = AuthenticationPassword(
user: userID,
identifier: AuthenticationUserIdentifier(user: userID),
password: oldPassword,
);
}
await super.changePassword(newPassword, auth: auth);
await super.changePassword(newPassword,
auth: auth, logoutDevices: logoutDevices);
} on MatrixException catch (matrixException) {
if (!matrixException.requireAdditionalAuthentication) {
rethrow;
@ -2031,11 +2029,11 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
return changePassword(
newPassword,
auth: AuthenticationPassword(
user: userID,
identifier: AuthenticationUserIdentifier(user: userID),
password: oldPassword,
session: matrixException.session,
),
logoutDevices: logoutDevices,
);
} catch (_) {
rethrow;

View File

@ -19,8 +19,8 @@
import 'dart:async';
import 'dart:convert';
import 'package:matrix/src/utils/space_child.dart';
import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/src/utils/space_child.dart';
import '../matrix.dart';
import 'client.dart';
@ -288,7 +288,7 @@ class Room {
if (!aliases.contains(canonicalAlias)) {
await client.setRoomAlias(canonicalAlias, id);
}
await client.setRoomStateWithKey(id, EventTypes.RoomCanonicalAlias, {
await client.setRoomStateWithKey(id, EventTypes.RoomCanonicalAlias, '', {
'alias': canonicalAlias,
});
}
@ -455,6 +455,7 @@ class Room {
Future<String> setName(String newName) => client.setRoomStateWithKey(
id,
EventTypes.RoomName,
'',
{'name': newName},
);
@ -462,6 +463,7 @@ class Room {
Future<String> setDescription(String newName) => client.setRoomStateWithKey(
id,
EventTypes.RoomTopic,
'',
{'topic': newName},
);
@ -506,14 +508,15 @@ class Room {
/// this works if there is no connection to the homeserver.
Future<void> setUnread(bool unread) async {
final content = MarkedUnread(unread).toJson();
await _handleFakeSync(SyncUpdate()
await _handleFakeSync(SyncUpdate(nextBatch: '')
..rooms = (RoomsUpdate()
..join = (({}..[id] = (JoinedRoomUpdate()
..accountData = [
BasicRoomEvent()
..content = content
..roomId = id
..type = EventType.markedUnread
BasicRoomEvent(
content: content,
roomId: id,
type: EventType.markedUnread,
)
])))));
await client.setAccountDataPerRoom(
client.userID,
@ -524,23 +527,24 @@ class Room {
if (unread == false && lastEvent != null) {
await setReadMarker(
lastEvent.eventId,
readReceiptLocationEventId: lastEvent.eventId,
mRead: lastEvent.eventId,
);
}
}
/// Returns true if this room has a m.favourite tag.
bool get isFavourite => tags[TagType.Favourite] != null;
bool get isFavourite => tags[TagType.favourite] != null;
/// Sets the m.favourite tag for this room.
Future<void> setFavourite(bool favourite) =>
favourite ? addTag(TagType.Favourite) : removeTag(TagType.Favourite);
favourite ? addTag(TagType.favourite) : removeTag(TagType.favourite);
/// Call the Matrix API to change the pinned events of this room.
Future<String> setPinnedEvents(List<String> pinnedEventIds) =>
client.setRoomStateWithKey(
id,
EventTypes.RoomPinnedEvents,
'',
{'pinned': pinnedEventIds},
);
@ -635,13 +639,13 @@ class Room {
}
final uploadResp = await client.uploadContent(
uploadFile.bytes,
uploadFile.name,
filename: uploadFile.name,
contentType: uploadFile.mimeType,
);
final thumbnailUploadResp = uploadThumbnail != null
? await client.uploadContent(
uploadThumbnail.bytes,
uploadThumbnail.name,
filename: uploadThumbnail.name,
contentType: uploadThumbnail.mimeType,
)
: null;
@ -751,17 +755,16 @@ class Room {
RegExp(r'<mx-reply>.*<\/mx-reply>',
caseSensitive: false, multiLine: false, dotAll: true),
'');
final repliedHtml = content.tryGet<String>(
'formatted_body',
final repliedHtml = content.tryGet<String>('formatted_body') ??
htmlEscape
.convert(content.tryGet<String>('body', ''))
.replaceAll('\n', '<br>'));
.convert(content.tryGet<String>('body') ?? '')
.replaceAll('\n', '<br>');
content['formatted_body'] =
'<mx-reply><blockquote><a href="https://matrix.to/#/${inReplyTo.room.id}/${inReplyTo.eventId}">In reply to</a> <a href="https://matrix.to/#/${inReplyTo.senderId}">${inReplyTo.senderId}</a><br>$replyHtml</blockquote></mx-reply>$repliedHtml';
// We escape all @room-mentions here to prevent accidental room pings when an admin
// replies to a message containing that!
content['body'] =
'${replyText.replaceAll('@room', '@\u200broom')}\n\n${content.tryGet<String>('body', '')}';
'${replyText.replaceAll('@room', '@\u200broom')}\n\n${content.tryGet<String>('body') ?? ''}';
content['m.relates_to'] = {
'm.in_reply_to': {
'event_id': inReplyTo.eventId,
@ -783,21 +786,22 @@ class Room {
}
}
final sentDate = DateTime.now();
final syncUpdate = SyncUpdate()
final syncUpdate = SyncUpdate(nextBatch: '')
..rooms = (RoomsUpdate()
..join = (<String, JoinedRoomUpdate>{}..[id] = (JoinedRoomUpdate()
..timeline = (TimelineUpdate()
..events = [
MatrixEvent()
..content = content
..type = type
..eventId = messageID
..senderId = client.userID
..originServerTs = sentDate
..unsigned = {
MatrixEvent(
content: content,
type: type,
eventId: messageID,
senderId: client.userID,
originServerTs: sentDate,
unsigned: {
messageSendingStatusKey: 0,
'transaction_id': messageID,
},
)
]))));
await _handleFakeSync(syncUpdate);
@ -868,7 +872,7 @@ class Room {
if ([MatrixError.M_NOT_FOUND, MatrixError.M_UNKNOWN]
.contains(exception.error)) {
await _handleFakeSync(
SyncUpdate()
SyncUpdate(nextBatch: '')
..rooms = (RoomsUpdate()
..leave = {
'$id': (LeftRoomUpdate()),
@ -909,12 +913,13 @@ class Room {
return await client.setRoomStateWithKey(
id,
EventTypes.RoomPowerLevels,
'',
powerMap,
);
}
/// Call the Matrix API to invite a user to this room.
Future<void> invite(String userID) => client.inviteToRoom(id, userID);
Future<void> invite(String userID) => client.inviteUser(id, userID);
/// Request more previous events from the server. [historyCount] defines how much events should
/// be received maximum. When the request is answered, [onHistoryReceived] will be triggered **before**
@ -936,7 +941,7 @@ class Room {
if (!((resp.chunk?.isNotEmpty ?? false) && resp.end != null)) return;
await client.handleSync(
SyncUpdate()
SyncUpdate(nextBatch: '')
..rooms = (RoomsUpdate()
..join = ({}..[id] = (JoinedRoomUpdate()
..state = resp.state
@ -1000,16 +1005,15 @@ class Room {
/// Sets the position of the read marker for a given room, and optionally the
/// read receipt's location.
Future<void> setReadMarker(String eventId,
{String readReceiptLocationEventId}) async {
if (readReceiptLocationEventId != null) {
Future<void> setReadMarker(String eventId, {String mRead}) async {
if (mRead != null) {
notificationCount = 0;
await client.database?.resetNotificationCount(client.id, id);
}
await client.setReadMarker(
id,
eventId,
readReceiptLocationEventId: readReceiptLocationEventId,
mRead: mRead,
);
return;
}
@ -1021,7 +1025,9 @@ class Room {
await client.database?.resetNotificationCount(client.id, id);
await client.postReceipt(
id,
ReceiptType.mRead,
eventId,
{},
);
return;
}
@ -1034,7 +1040,7 @@ class Room {
await client.setReadMarker(
id,
eventID,
readReceiptLocationEventId: eventID,
mRead: eventID,
);
return;
}
@ -1294,10 +1300,12 @@ class Room {
/// Uploads a new user avatar for this room. Returns the event ID of the new
/// m.room.avatar event.
Future<String> setAvatar(MatrixFile file) async {
final uploadResp = await client.uploadContent(file.bytes, file.name);
final uploadResp =
await client.uploadContent(file.bytes, filename: file.name);
return await client.setRoomStateWithKey(
id,
EventTypes.RoomAvatar,
'',
{'url': uploadResp},
);
}
@ -1402,14 +1410,14 @@ class Room {
'global',
PushRuleKind.room,
id,
[PushRuleAction.dont_notify],
[PushRuleAction.dontNotify],
);
} else if (pushRuleState == PushRuleState.notify) {
await client.setPushRule(
'global',
PushRuleKind.room,
id,
[PushRuleAction.dont_notify],
[PushRuleAction.dontNotify],
);
}
break;
@ -1422,9 +1430,9 @@ class Room {
'global',
PushRuleKind.override,
id,
[PushRuleAction.dont_notify],
[PushRuleAction.dontNotify],
conditions: [
PushConditions('event_match', key: 'room_id', pattern: id)
PushCondition(kind: 'event_match', key: 'room_id', pattern: id)
],
);
}
@ -1578,6 +1586,7 @@ class Room {
await client.setRoomStateWithKey(
id,
EventTypes.RoomJoinRules,
'',
{
'join_rule': joinRules.toString().replaceAll('JoinRules.', ''),
},
@ -1600,6 +1609,7 @@ class Room {
await client.setRoomStateWithKey(
id,
EventTypes.GuestAccess,
'',
{
'guest_access': _guestAccessMap[guestAccess],
},
@ -1623,6 +1633,7 @@ class Room {
await client.setRoomStateWithKey(
id,
EventTypes.HistoryVisibility,
'',
{
'history_visibility': _historyVisibilityMap[historyVisibility],
},
@ -1648,6 +1659,7 @@ class Room {
await client.setRoomStateWithKey(
id,
EventTypes.Encryption,
'',
{
'algorithm': algorithm,
},
@ -1742,22 +1754,14 @@ class Room {
}) async {
if (!isSpace) throw Exception('Room is not a space!');
via ??= [roomId.domain];
await client.setRoomStateWithKey(
id,
EventTypes.spaceChild,
{
'via': via,
if (order != null) 'order': order,
if (suggested != null) 'suggested': suggested,
},
roomId);
await client.setRoomStateWithKey(
roomId,
EventTypes.spaceParent,
{
'via': via,
},
id);
await client.setRoomStateWithKey(id, EventTypes.spaceChild, roomId, {
'via': via,
if (order != null) 'order': order,
if (suggested != null) 'suggested': suggested,
});
await client.setRoomStateWithKey(roomId, EventTypes.spaceParent, id, {
'via': via,
});
return;
}

View File

@ -172,8 +172,8 @@ extension CommandsClientExtension on Client {
return await args.room.client.setRoomStateWithKey(
args.room.id,
EventTypes.RoomMember,
currentEventJson,
args.room.client.userID,
currentEventJson,
);
});
addCommand('myroomavatar', (CommandArgs args) async {
@ -185,8 +185,8 @@ extension CommandsClientExtension on Client {
return await args.room.client.setRoomStateWithKey(
args.room.id,
EventTypes.RoomMember,
currentEventJson,
args.room.client.userID,
currentEventJson,
);
});
}

View File

@ -23,7 +23,6 @@ import 'package:matrix/matrix.dart';
import 'package:olm/olm.dart' as olm;
import '../../encryption.dart';
import '../client.dart';
import '../event.dart';
import '../room.dart';
@ -132,12 +131,22 @@ class DeviceKeysList {
DeviceKeysList(this.userId, this.client);
}
class SimpleSignableKey extends MatrixSignableKey {
@override
String identifier;
SimpleSignableKey.fromJson(Map<String, dynamic> json) : super.fromJson(json);
}
abstract class SignableKey extends MatrixSignableKey {
Client client;
Map<String, dynamic> validSignatures;
bool _verified;
bool blocked;
@override
String identifier;
String get ed25519Key => keys['ed25519:$identifier'];
bool get verified => (directVerified || crossVerified) && !blocked;
bool get encryptToDevice =>
@ -161,8 +170,8 @@ abstract class SignableKey extends MatrixSignableKey {
blocked = false;
}
MatrixSignableKey cloneForSigning() {
final newKey = MatrixSignableKey.fromJson(toJson().copy());
SimpleSignableKey cloneForSigning() {
final newKey = SimpleSignableKey.fromJson(toJson().copy());
newKey.identifier = identifier;
newKey.signatures ??= <String, Map<String, String>>{};
newKey.signatures.clear();

View File

@ -30,7 +30,7 @@ class SpaceChild {
: assert(state.type == EventTypes.spaceChild),
roomId = state.stateKey,
via = state.content.tryGetList<String>('via'),
order = state.content.tryGet<String>('order', ''),
order = state.content.tryGet<String>('order') ?? '',
suggested = state.content.tryGet<bool>('suggested');
}

View File

@ -44,6 +44,7 @@ class UiaRequest<T> {
Map<String, dynamic> params = <String, dynamic>{};
UiaRequestState get state => _state;
set state(UiaRequestState newState) {
if (_state == newState) return;
_state = newState;
@ -57,7 +58,8 @@ class UiaRequest<T> {
Future<T> _run([AuthenticationData auth]) async {
state = UiaRequestState.loading;
try {
auth ??= AuthenticationData(session: session);
auth ??=
AuthenticationData(session: session, type: AuthenticationTypes.token);
final res = await request(auth);
state = UiaRequestState.done;
result = res;

View File

@ -17,7 +17,7 @@ dependencies:
crypto: ^3.0.0
base58check: ^2.0.0
olm: ^2.0.0
matrix_api_lite: ^0.3.5
matrix_api_lite: ^0.4.0
hive: ^2.0.4
ffi: ^1.0.0
js: ^0.6.3

View File

@ -84,7 +84,7 @@ void main() {
try {
await matrix.checkHomeserver('https://fakeserver.wrongaddress');
} on MatrixConnectionException catch (exception) {
} catch (exception) {
expect(exception != null, true);
}
await matrix.checkHomeserver('https://fakeserver.notexisting',
@ -323,7 +323,7 @@ void main() {
await matrix.checkHomeserver('https://fakeserver.notexisting',
checkWellKnown: false);
final loginResp = await matrix.login(
final loginResp = await matrix.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: 'test'),
password: '1234');
@ -368,12 +368,12 @@ void main() {
final profile = await matrix.getProfileFromUserId('@getme:example.com',
getFromRooms: false);
expect(profile.avatarUrl.toString(), 'mxc://test');
expect(profile.displayname, 'You got me');
expect(profile.displayName, 'You got me');
final aliceProfile =
await matrix.getProfileFromUserId('@alice:example.com');
expect(aliceProfile.avatarUrl.toString(),
'mxc://example.org/SEsfnsuifSDFSSEF');
expect(aliceProfile.displayname, 'Alice Margatroid');
expect(aliceProfile.displayName, 'Alice Margatroid');
});
test('sendToDeviceEncrypted', () async {
if (!olmEnabled) {
@ -642,7 +642,8 @@ void main() {
});
test('upload', () async {
final client = await getClient();
final response = await client.uploadContent(Uint8List(0), 'file.jpeg');
final response =
await client.uploadContent(Uint8List(0), filename: 'file.jpeg');
expect(response, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw');
expect(await client.database.getFile(response) != null,
client.database.supportsFileStoring);

View File

@ -269,7 +269,7 @@ void main() {
final matrix = Client('testclient', httpClient: FakeMatrixApi());
await matrix.checkHomeserver('https://fakeserver.notexisting',
checkWellKnown: false);
await matrix.login(
await matrix.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: 'test'),
password: '1234');
@ -288,7 +288,7 @@ void main() {
final matrix = Client('testclient', httpClient: FakeMatrixApi());
await matrix.checkHomeserver('https://fakeserver.notexisting',
checkWellKnown: false);
await matrix.login(
await matrix.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: 'test'),
password: '1234');

View File

@ -16,15 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
import 'dart:convert';
import 'dart:core';
import 'dart:math';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
Map<String, dynamic> decodeJson(dynamic data) {
if (data is String) {
@ -122,11 +121,9 @@ class FakeMatrixApi extends MockClient {
action.contains('/account_data/') &&
!action.contains('/room/')) {
final type = Uri.decodeComponent(action.split('/').last);
final syncUpdate = sdk.SyncUpdate()
final syncUpdate = sdk.SyncUpdate(nextBatch: '')
..accountData = [
sdk.BasicEvent()
..content = decodeJson(data)
..type = type
sdk.BasicEvent(content: decodeJson(data), type: type)
];
if (client.database != null) {
await client.database.transaction(() async {
@ -2111,7 +2108,7 @@ class FakeMatrixApi extends MockClient {
}
}
// and generate a fake sync
client.handleSync(sdk.SyncUpdate());
client.handleSync(sdk.SyncUpdate(nextBatch: ''));
}
return {};
},

View File

@ -648,8 +648,8 @@ void main() {
});
test('Test tag methods', () async {
await room.addTag(TagType.Favourite, order: 0.1);
await room.removeTag(TagType.Favourite);
await room.addTag(TagType.favourite, order: 0.1);
await room.removeTag(TagType.favourite);
expect(room.isFavourite, false);
room.roomAccountData['m.tag'] = BasicRoomEvent.fromJson({
'content': {
@ -661,7 +661,7 @@ void main() {
'type': 'm.tag'
});
expect(room.tags.length, 1);
expect(room.tags[TagType.Favourite].order, 0.1);
expect(room.tags[TagType.favourite].order, 0.1);
expect(room.isFavourite, true);
await room.setFavourite(false);
});

View File

@ -49,7 +49,7 @@ void main() {
setUp(() async {
await client.checkHomeserver('https://fakeserver.notexisting',
checkWellKnown: false);
await client.login(
await client.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: 'test'),
password: '1234');
});

View File

@ -22,7 +22,7 @@ void test() async {
Logs().i('++++ Login Alice at ++++');
testClientA = Client('TestClientA', databaseBuilder: getDatabase);
await testClientA.checkHomeserver(TestUser.homeserver);
await testClientA.login(
await testClientA.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: TestUser.username),
password: TestUser.password);
assert(testClientA.encryptionEnabled);
@ -30,7 +30,7 @@ void test() async {
Logs().i('++++ Login Bob ++++');
testClientB = Client('TestClientB', databaseBuilder: getDatabase);
await testClientB.checkHomeserver(TestUser.homeserver);
await testClientB.login(
await testClientB.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: TestUser.username2),
password: TestUser.password);
assert(testClientB.encryptionEnabled);
@ -220,7 +220,7 @@ void test() async {
Logs().i('++++ Login Bob in another client ++++');
var testClientC = Client('TestClientC', databaseBuilder: getDatabase);
await testClientC.checkHomeserver(TestUser.homeserver);
await testClientC.login(
await testClientC.login(LoginType.mLoginPassword,
identifier: AuthenticationUserIdentifier(user: TestUser.username2),
password: TestUser.password);
await Future.delayed(Duration(seconds: 3));