Client feature add device tracking
This commit is contained in:
parent
e1335ae2f3
commit
edd8aa5c4c
|
@ -26,6 +26,7 @@ library famedlysdk;
|
|||
export 'package:famedlysdk/src/sync/room_update.dart';
|
||||
export 'package:famedlysdk/src/sync/event_update.dart';
|
||||
export 'package:famedlysdk/src/sync/user_update.dart';
|
||||
export 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_exception.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
export 'package:famedlysdk/src/utils/mx_content.dart';
|
||||
|
|
|
@ -29,6 +29,7 @@ import 'package:famedlysdk/src/account_data.dart';
|
|||
import 'package:famedlysdk/src/presence.dart';
|
||||
import 'package:famedlysdk/src/store_api.dart';
|
||||
import 'package:famedlysdk/src/sync/user_update.dart';
|
||||
import 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
import 'package:famedlysdk/src/utils/open_id_credentials.dart';
|
||||
import 'package:famedlysdk/src/utils/turn_server_credentials.dart';
|
||||
|
@ -540,6 +541,12 @@ class Client {
|
|||
}
|
||||
|
||||
static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}';
|
||||
static const List<String> supportedDirectEncryptionAlgorithms = [
|
||||
"m.olm.v1.curve25519-aes-sha2"
|
||||
];
|
||||
static const List<String> supportedGroupEncryptionAlgorithms = [
|
||||
"m.megolm.v1.aes-sha2"
|
||||
];
|
||||
|
||||
http.Client httpClient = http.Client();
|
||||
|
||||
|
@ -630,15 +637,16 @@ class Client {
|
|||
/// ```
|
||||
///
|
||||
/// Sends [LoginState.logged] to [onLoginStateChanged].
|
||||
void connect(
|
||||
{String newToken,
|
||||
String newHomeserver,
|
||||
String newUserID,
|
||||
String newDeviceName,
|
||||
String newDeviceID,
|
||||
List<String> newMatrixVersions,
|
||||
bool newLazyLoadMembers,
|
||||
String newPrevBatch}) async {
|
||||
void connect({
|
||||
String newToken,
|
||||
String newHomeserver,
|
||||
String newUserID,
|
||||
String newDeviceName,
|
||||
String newDeviceID,
|
||||
List<String> newMatrixVersions,
|
||||
bool newLazyLoadMembers,
|
||||
String newPrevBatch,
|
||||
}) async {
|
||||
this._accessToken = newToken;
|
||||
this._homeserver = newHomeserver;
|
||||
this._userID = newUserID;
|
||||
|
@ -650,6 +658,7 @@ class Client {
|
|||
|
||||
if (this.storeAPI != null) {
|
||||
await this.storeAPI.storeClient();
|
||||
_userDeviceKeys = await this.storeAPI.getUserDeviceKeys();
|
||||
if (this.store != null) {
|
||||
this._rooms = await this.store.getRoomList(onlyLeft: false);
|
||||
this._sortRooms();
|
||||
|
@ -833,6 +842,7 @@ class Client {
|
|||
_sortRooms();
|
||||
}
|
||||
this.prevBatch = syncResp["next_batch"];
|
||||
unawaited(_updateUserDeviceKeys());
|
||||
if (hash == _syncRequest.hashCode) unawaited(_sync());
|
||||
} on MatrixException catch (exception) {
|
||||
onError.add(exception);
|
||||
|
@ -866,9 +876,29 @@ class Client {
|
|||
sync["to_device"]["events"] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync["to_device"]["events"], "to_device");
|
||||
}
|
||||
if (sync["device_lists"] is Map<String, dynamic>) {
|
||||
_handleDeviceListsEvents(sync["device_lists"]);
|
||||
}
|
||||
onSync.add(sync);
|
||||
}
|
||||
|
||||
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists) {
|
||||
if (deviceLists["changed"] is List) {
|
||||
for (final userId in deviceLists["changed"]) {
|
||||
print("The device list of $userId has changed. Mark as outdated!");
|
||||
if (_userDeviceKeys.containsKey(userId)) {
|
||||
_userDeviceKeys[userId].outdated = true;
|
||||
}
|
||||
}
|
||||
for (final userId in deviceLists["left"]) {
|
||||
print("The device list of $userId is no longer relevant! Remove it!");
|
||||
if (_userDeviceKeys.containsKey(userId)) {
|
||||
_userDeviceKeys.remove(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
||||
rooms.forEach((String id, dynamic room) async {
|
||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||
|
@ -1011,6 +1041,13 @@ class Client {
|
|||
|
||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
||||
if (event["type"] is String && event["content"] is Map<String, dynamic>) {
|
||||
// The client must ignore any new m.room.encryption event to prevent
|
||||
// man-in-the-middle attacks!
|
||||
if (event["type"] == "m.room.encryption" &&
|
||||
getRoomById(roomID).encrypted) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventUpdate update = EventUpdate(
|
||||
eventType: event["type"],
|
||||
roomID: roomID,
|
||||
|
@ -1162,4 +1199,65 @@ class Client {
|
|||
);
|
||||
return OpenIdCredentials.fromJson(response);
|
||||
}
|
||||
|
||||
/// A map of known device keys per user.
|
||||
Map<String, DeviceKeysList> get userDeviceKeys => _userDeviceKeys;
|
||||
Map<String, DeviceKeysList> _userDeviceKeys = {};
|
||||
|
||||
Future<Set<String>> _getUserIdsInEncryptedRooms() async {
|
||||
Set<String> userIds = {};
|
||||
for (int i = 0; i < rooms.length; i++) {
|
||||
if (rooms[i].encrypted) {
|
||||
List<User> userList = await rooms[i].requestParticipants();
|
||||
for (User user in userList) {
|
||||
userIds.add(user.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return userIds;
|
||||
}
|
||||
|
||||
Future<void> _updateUserDeviceKeys() async {
|
||||
Set<String> trackedUserIds = await _getUserIdsInEncryptedRooms();
|
||||
print("We are tracking the devices of these users:");
|
||||
print(trackedUserIds);
|
||||
|
||||
// Remove all userIds we no longer need to track the devices of.
|
||||
_userDeviceKeys
|
||||
.removeWhere((String userId, v) => !trackedUserIds.contains(userId));
|
||||
|
||||
// Check if there are outdated device key lists. Add it to the set.
|
||||
Map<String, dynamic> outdatedLists = {};
|
||||
for (String userId in trackedUserIds) {
|
||||
if (!userDeviceKeys.containsKey(userId)) {
|
||||
print("Create new device list for user $userId");
|
||||
_userDeviceKeys[userId] = DeviceKeysList(userId);
|
||||
}
|
||||
DeviceKeysList deviceKeysList = userDeviceKeys[userId];
|
||||
if (deviceKeysList.outdated) {
|
||||
print(
|
||||
"The device keys list of $userId is outdated. Add to the request");
|
||||
outdatedLists[userId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Request the missing device key lists.
|
||||
if (outdatedLists.isNotEmpty) {
|
||||
final Map<String, dynamic> response = await this.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: "/client/r0/keys/query",
|
||||
data: {"timeout": 10000, "device_keys": outdatedLists});
|
||||
for (final rawDeviceKeyListEntry in response["device_keys"].entries) {
|
||||
final String userId = rawDeviceKeyListEntry.key;
|
||||
_userDeviceKeys[userId].deviceKeys = {};
|
||||
print("Got device key list of $userId. Store it now!");
|
||||
for (final rawDeviceKeyEntry in rawDeviceKeyListEntry.value.entries) {
|
||||
_userDeviceKeys[userId].deviceKeys[rawDeviceKeyEntry.key] =
|
||||
DeviceKeys.fromJson(rawDeviceKeyEntry.value);
|
||||
}
|
||||
_userDeviceKeys[userId].outdated = false;
|
||||
}
|
||||
}
|
||||
await this.storeAPI?.storeUserDeviceKeys(userDeviceKeys);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,9 +208,9 @@ class Event {
|
|||
return EventTypes.Sticker;
|
||||
case "m.room.message":
|
||||
return EventTypes.Message;
|
||||
case "m.call.encrypted":
|
||||
case "m.room.encrypted":
|
||||
return EventTypes.Encrypted;
|
||||
case "m.call.encryption":
|
||||
case "m.room.encryption":
|
||||
return EventTypes.Encryption;
|
||||
case "m.call.invite":
|
||||
return EventTypes.CallInvite;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/client.dart';
|
||||
import 'package:famedlysdk/src/event.dart';
|
||||
import 'package:famedlysdk/src/room_account_data.dart';
|
||||
|
@ -761,6 +762,7 @@ class Room {
|
|||
/// Request the full list of participants from the server. The local list
|
||||
/// from the store is not complete if the client uses lazy loading.
|
||||
Future<List<User>> requestParticipants() async {
|
||||
if (participantListComplete) return getParticipants();
|
||||
List<User> participants = [];
|
||||
|
||||
dynamic res = await client.jsonRequest(
|
||||
|
@ -768,12 +770,24 @@ class Room {
|
|||
|
||||
for (num i = 0; i < res["chunk"].length; i++) {
|
||||
User newUser = Event.fromJson(res["chunk"][i], this).asUser;
|
||||
if (newUser.membership != Membership.leave) participants.add(newUser);
|
||||
if (![Membership.leave, Membership.ban].contains(newUser.membership)) {
|
||||
participants.add(newUser);
|
||||
setState(newUser);
|
||||
}
|
||||
}
|
||||
|
||||
return participants;
|
||||
}
|
||||
|
||||
/// Checks if the local participant list of joined and invited users is complete.
|
||||
bool get participantListComplete {
|
||||
List<User> knownParticipants = getParticipants();
|
||||
knownParticipants.removeWhere(
|
||||
(u) => ![Membership.join, Membership.invite].contains(u.membership));
|
||||
return knownParticipants.length ==
|
||||
(this.mJoinedMemberCount ?? 0) + (this.mInvitedMemberCount ?? 0);
|
||||
}
|
||||
|
||||
/// Returns the [User] object for the given [mxID] or requests it from
|
||||
/// the homeserver and waits for a response.
|
||||
Future<User> getUserByMXID(String mxID) async {
|
||||
|
@ -1235,4 +1249,42 @@ class Room {
|
|||
/// Whether the user has the permission to change the history visibility.
|
||||
bool get canChangeHistoryVisibility =>
|
||||
canSendEvent("m.room.history_visibility");
|
||||
|
||||
/// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported.
|
||||
/// Returns null if there is no encryption algorithm.
|
||||
String get encryptionAlgorithm => getState("m.room.encryption") != null
|
||||
? getState("m.room.encryption").content["algorithm"].toString()
|
||||
: null;
|
||||
|
||||
/// Checks if this room is encrypted.
|
||||
bool get encrypted => encryptionAlgorithm != null;
|
||||
|
||||
Future<void> enableEncryption({int algorithmIndex = 0}) async {
|
||||
if (encrypted) throw ("Encryption is already enabled!");
|
||||
final String algorithm =
|
||||
Client.supportedGroupEncryptionAlgorithms[algorithmIndex];
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: "/client/r0/rooms/$id/state/m.room.encryption/",
|
||||
data: {
|
||||
"algorithm": algorithm,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<List<DeviceKeys>> getUserDeviceKeys() async {
|
||||
List<DeviceKeys> deviceKeys = [];
|
||||
List<User> users = await requestParticipants();
|
||||
for (final userDeviceKeyEntry in client.userDeviceKeys.entries) {
|
||||
if (users.indexWhere((u) => u.id == userDeviceKeyEntry.key) == -1) {
|
||||
continue;
|
||||
}
|
||||
for (DeviceKeys deviceKeyEntry
|
||||
in userDeviceKeyEntry.value.deviceKeys.values) {
|
||||
deviceKeys.add(deviceKeyEntry);
|
||||
}
|
||||
}
|
||||
return deviceKeys;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import 'dart:async';
|
|||
import 'dart:core';
|
||||
import 'package:famedlysdk/src/account_data.dart';
|
||||
import 'package:famedlysdk/src/presence.dart';
|
||||
import 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||
import 'client.dart';
|
||||
import 'event.dart';
|
||||
import 'room.dart';
|
||||
|
@ -46,6 +47,10 @@ abstract class StoreAPI {
|
|||
|
||||
/// Clears all tables from the database.
|
||||
Future<void> clear();
|
||||
|
||||
Future<void> storeUserDeviceKeys(Map<String, DeviceKeysList> userDeviceKeys);
|
||||
|
||||
Future<Map<String, DeviceKeysList>> getUserDeviceKeys();
|
||||
}
|
||||
|
||||
/// Responsible to store all data persistent and to query objects from the
|
||||
|
|
102
lib/src/utils/device_keys_list.dart
Normal file
102
lib/src/utils/device_keys_list.dart
Normal file
|
@ -0,0 +1,102 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import '../client.dart';
|
||||
|
||||
class DeviceKeysList {
|
||||
String userId;
|
||||
bool outdated = true;
|
||||
Map<String, DeviceKeys> deviceKeys = {};
|
||||
|
||||
DeviceKeysList.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
outdated = json['outdated'];
|
||||
deviceKeys = {};
|
||||
for (final rawDeviceKeyEntry in json['device_keys'].entries) {
|
||||
deviceKeys[rawDeviceKeyEntry.key] =
|
||||
DeviceKeys.fromJson(rawDeviceKeyEntry.value);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = Map<String, dynamic>();
|
||||
data['user_id'] = this.userId;
|
||||
data['outdated'] = this.outdated;
|
||||
|
||||
Map<String, dynamic> rawDeviceKeys = {};
|
||||
for (final deviceKeyEntry in this.deviceKeys.entries) {
|
||||
rawDeviceKeys[deviceKeyEntry.key] = deviceKeyEntry.value.toJson();
|
||||
}
|
||||
data['device_keys'] = rawDeviceKeys;
|
||||
return data;
|
||||
}
|
||||
|
||||
String toString() => json.encode(toJson());
|
||||
|
||||
DeviceKeysList(this.userId);
|
||||
}
|
||||
|
||||
class DeviceKeys {
|
||||
String userId;
|
||||
String deviceId;
|
||||
List<String> algorithms;
|
||||
Map<String, String> keys;
|
||||
Map<String, dynamic> signatures;
|
||||
Map<String, dynamic> unsigned;
|
||||
bool verified;
|
||||
bool blocked;
|
||||
|
||||
Future<void> setVerified(bool newVerified, Client client) {
|
||||
verified = newVerified;
|
||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
||||
}
|
||||
|
||||
Future<void> setBlocked(bool newBlocked, Client client) {
|
||||
blocked = newBlocked;
|
||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
||||
}
|
||||
|
||||
DeviceKeys({
|
||||
this.userId,
|
||||
this.deviceId,
|
||||
this.algorithms,
|
||||
this.keys,
|
||||
this.signatures,
|
||||
this.unsigned,
|
||||
this.verified,
|
||||
this.blocked,
|
||||
});
|
||||
|
||||
DeviceKeys.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
deviceId = json['device_id'];
|
||||
algorithms = json['algorithms'].cast<String>();
|
||||
keys = json['keys'] != null ? Map<String, String>.from(json['keys']) : null;
|
||||
signatures = json['signatures'] != null
|
||||
? Map<String, dynamic>.from(json['signatures'])
|
||||
: null;
|
||||
unsigned = json['unsigned'] != null
|
||||
? Map<String, dynamic>.from(json['unsigned'])
|
||||
: null;
|
||||
verified = json['verified'] ?? false;
|
||||
blocked = json['blocked'] ?? false;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = Map<String, dynamic>();
|
||||
data['user_id'] = this.userId;
|
||||
data['device_id'] = this.deviceId;
|
||||
data['algorithms'] = this.algorithms;
|
||||
if (this.keys != null) {
|
||||
data['keys'] = this.keys;
|
||||
}
|
||||
if (this.signatures != null) {
|
||||
data['signatures'] = this.signatures;
|
||||
}
|
||||
if (this.unsigned != null) {
|
||||
data['unsigned'] = this.unsigned;
|
||||
}
|
||||
data['verified'] = this.verified;
|
||||
data['blocked'] = this.blocked;
|
||||
return data;
|
||||
}
|
||||
}
|
16
pubspec.lock
16
pubspec.lock
|
@ -212,19 +212,12 @@ packages:
|
|||
source: hosted
|
||||
version: "0.6.1+1"
|
||||
json_annotation:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
json_serializable:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
kernel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -365,13 +358,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.4+2"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -10,10 +10,8 @@ environment:
|
|||
dependencies:
|
||||
http: ^0.12.0+2
|
||||
mime_type: ^0.2.4
|
||||
json_annotation: ^2.4.0
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.0.0
|
||||
build_runner: ^1.5.2
|
||||
json_serializable: ^3.0.0
|
||||
pedantic: ^1.5.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL
|
|
@ -138,6 +138,9 @@ void main() {
|
|||
expect(matrix.rooms[1].typingUsers.length, 1);
|
||||
expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||
expect(matrix.rooms[1].encrypted, true);
|
||||
expect(matrix.rooms[1].encryptionAlgorithm,
|
||||
Client.supportedGroupEncryptionAlgorithms.first);
|
||||
expect(
|
||||
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||
.content["@alice:example.com"]["ts"],
|
||||
|
@ -151,11 +154,33 @@ void main() {
|
|||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||
final List<User> contacts = await matrix.loadFamedlyContacts();
|
||||
expect(contacts.length, 1);
|
||||
expect(contacts[0].senderId, "@alice:example.org");
|
||||
expect(contacts[0].senderId, "@alice:example.com");
|
||||
expect(
|
||||
matrix.presences["@alice:example.com"].presence, PresenceType.online);
|
||||
expect(presenceCounter, 1);
|
||||
expect(accountDataCounter, 3);
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.userDeviceKeys.length, 1);
|
||||
expect(matrix.userDeviceKeys["@alice:example.com"].outdated, false);
|
||||
expect(matrix.userDeviceKeys["@alice:example.com"].deviceKeys.length, 1);
|
||||
expect(
|
||||
matrix.userDeviceKeys["@alice:example.com"].deviceKeys["JLAFKJWSCS"]
|
||||
.verified,
|
||||
false);
|
||||
|
||||
matrix.handleSync({
|
||||
"device_lists": {
|
||||
"changed": [
|
||||
"@alice:example.com",
|
||||
],
|
||||
"left": [
|
||||
"@bob:example.com",
|
||||
],
|
||||
}
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.userDeviceKeys.length, 1);
|
||||
expect(matrix.userDeviceKeys["@alice:example.com"].outdated, true);
|
||||
|
||||
matrix.handleSync({
|
||||
"rooms": {
|
||||
|
@ -184,6 +209,7 @@ void main() {
|
|||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
||||
null);
|
||||
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
||||
altContacts.forEach((u) => print(u.id));
|
||||
expect(altContacts.length, 2);
|
||||
expect(altContacts[0].senderId, "@alice:example.com");
|
||||
});
|
||||
|
@ -248,7 +274,7 @@ void main() {
|
|||
|
||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||
|
||||
expect(eventUpdateList.length, 12);
|
||||
expect(eventUpdateList.length, 13);
|
||||
|
||||
expect(eventUpdateList[0].eventType, "m.room.member");
|
||||
expect(eventUpdateList[0].roomID, "!726s6s6q:example.com");
|
||||
|
@ -258,41 +284,45 @@ void main() {
|
|||
expect(eventUpdateList[1].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[1].type, "state");
|
||||
|
||||
expect(eventUpdateList[2].eventType, "m.room.member");
|
||||
expect(eventUpdateList[2].eventType, "m.room.encryption");
|
||||
expect(eventUpdateList[2].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[2].type, "timeline");
|
||||
expect(eventUpdateList[2].type, "state");
|
||||
|
||||
expect(eventUpdateList[3].eventType, "m.room.message");
|
||||
expect(eventUpdateList[3].eventType, "m.room.member");
|
||||
expect(eventUpdateList[3].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[3].type, "timeline");
|
||||
|
||||
expect(eventUpdateList[4].eventType, "m.typing");
|
||||
expect(eventUpdateList[4].eventType, "m.room.message");
|
||||
expect(eventUpdateList[4].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[4].type, "ephemeral");
|
||||
expect(eventUpdateList[4].type, "timeline");
|
||||
|
||||
expect(eventUpdateList[5].eventType, "m.receipt");
|
||||
expect(eventUpdateList[5].eventType, "m.typing");
|
||||
expect(eventUpdateList[5].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[5].type, "ephemeral");
|
||||
|
||||
expect(eventUpdateList[6].eventType, "m.receipt");
|
||||
expect(eventUpdateList[6].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[6].type, "account_data");
|
||||
expect(eventUpdateList[6].type, "ephemeral");
|
||||
|
||||
expect(eventUpdateList[7].eventType, "m.tag");
|
||||
expect(eventUpdateList[7].eventType, "m.receipt");
|
||||
expect(eventUpdateList[7].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[7].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[8].eventType, "org.example.custom.room.config");
|
||||
expect(eventUpdateList[8].eventType, "m.tag");
|
||||
expect(eventUpdateList[8].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[8].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[9].eventType, "m.room.name");
|
||||
expect(eventUpdateList[9].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[9].type, "invite_state");
|
||||
expect(eventUpdateList[9].eventType, "org.example.custom.room.config");
|
||||
expect(eventUpdateList[9].roomID, "!726s6s6q:example.com");
|
||||
expect(eventUpdateList[9].type, "account_data");
|
||||
|
||||
expect(eventUpdateList[10].eventType, "m.room.member");
|
||||
expect(eventUpdateList[10].eventType, "m.room.name");
|
||||
expect(eventUpdateList[10].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[10].type, "invite_state");
|
||||
|
||||
expect(eventUpdateList[11].eventType, "m.room.member");
|
||||
expect(eventUpdateList[11].roomID, "!696r7674:example.com");
|
||||
expect(eventUpdateList[11].type, "invite_state");
|
||||
});
|
||||
|
||||
test('User Update Test', () async {
|
||||
|
|
78
test/device_keys_list_test.dart
Normal file
78
test/device_keys_list_test.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to device keys
|
||||
group("Device keys", () {
|
||||
test("fromJson", () async {
|
||||
Map<String, dynamic> rawJson = {
|
||||
"user_id": "@alice:example.com",
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"algorithms": ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS":
|
||||
"3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
|
||||
"ed25519:JLAFKJWSCS": "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.com": {
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {"device_display_name": "Alice's mobile phone"},
|
||||
"verified": false,
|
||||
"blocked": true,
|
||||
};
|
||||
Map<String, dynamic> rawListJson = {
|
||||
"user_id": "@alice:example.com",
|
||||
"outdated": true,
|
||||
"device_keys": {"JLAFKJWSCS": rawJson},
|
||||
};
|
||||
|
||||
Map<String, DeviceKeysList> userDeviceKeys = {
|
||||
"@alice:example.com": DeviceKeysList.fromJson(rawListJson),
|
||||
};
|
||||
Map<String, dynamic> userDeviceKeyRaw = {
|
||||
"@alice:example.com": rawListJson,
|
||||
};
|
||||
|
||||
expect(json.encode(DeviceKeys.fromJson(rawJson).toJson()),
|
||||
json.encode(rawJson));
|
||||
expect(json.encode(DeviceKeysList.fromJson(rawListJson).toJson()),
|
||||
json.encode(rawListJson));
|
||||
|
||||
Map<String, DeviceKeysList> mapFromRaw = {};
|
||||
for (final rawListEntry in userDeviceKeyRaw.entries) {
|
||||
mapFromRaw[rawListEntry.key] =
|
||||
DeviceKeysList.fromJson(rawListEntry.value);
|
||||
}
|
||||
expect(mapFromRaw.toString(), userDeviceKeys.toString());
|
||||
});
|
||||
});
|
||||
}
|
|
@ -364,7 +364,15 @@ class FakeMatrixApi extends MockClient {
|
|||
"state_key": "",
|
||||
"origin_server_ts": 1417731086796,
|
||||
"event_id": "66697273743032:example.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
"sender": "@alice:example.com",
|
||||
"type": "m.room.encryption",
|
||||
"state_key": "",
|
||||
"content": {"algorithm": "m.megolm.v1.aes-sha2"},
|
||||
"origin_server_ts": 1417731086795,
|
||||
"event_id": "666972737430353:example.com"
|
||||
},
|
||||
]
|
||||
},
|
||||
"timeline": {
|
||||
|
@ -582,10 +590,10 @@ class FakeMatrixApi extends MockClient {
|
|||
"type": "m.room.member",
|
||||
"event_id": "§143273582443PhrSn:example.org",
|
||||
"room_id": "!636q39766251:example.com",
|
||||
"sender": "@alice:example.org",
|
||||
"sender": "@alice:example.com",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"unsigned": {"age": 1234},
|
||||
"state_key": "@alice:example.org"
|
||||
"state_key": "@alice:example.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -760,6 +768,34 @@ class FakeMatrixApi extends MockClient {
|
|||
{"available": true},
|
||||
},
|
||||
"POST": {
|
||||
"/client/r0/keys/query": (var req) => {
|
||||
"failures": {},
|
||||
"device_keys": {
|
||||
"@alice:example.com": {
|
||||
"JLAFKJWSCS": {
|
||||
"user_id": "@alice:example.com",
|
||||
"device_id": "JLAFKJWSCS",
|
||||
"algorithms": [
|
||||
"m.olm.v1.curve25519-aes-sha2",
|
||||
"m.megolm.v1.aes-sha2"
|
||||
],
|
||||
"keys": {
|
||||
"curve25519:JLAFKJWSCS":
|
||||
"3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
|
||||
},
|
||||
"signatures": {
|
||||
"@alice:example.com": {
|
||||
"ed25519:JLAFKJWSCS":
|
||||
"dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA"
|
||||
}
|
||||
},
|
||||
"unsigned": {"device_display_name": "Alice's mobile phone"}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/client/r0/register": (var req) => {"user_id": "@testuser:example.com"},
|
||||
"/client/r0/register?kind=user": (var req) =>
|
||||
{"user_id": "@testuser:example.com"},
|
||||
|
|
|
@ -298,8 +298,8 @@ void main() {
|
|||
content: {"displayname": "alice"},
|
||||
stateKey: "@alice:test.abc"));
|
||||
final List<User> userList = room.getParticipants();
|
||||
expect(userList.length, 4);
|
||||
expect(userList[3].displayName, "alice");
|
||||
expect(userList.length, 5);
|
||||
expect(userList[3].displayName, "Alice Margatroid");
|
||||
});
|
||||
|
||||
test("addToDirectChat", () async {
|
||||
|
|
Loading…
Reference in a new issue