Merge branch 'client-feature-add-device-tracking' into 'master'
Client feature add device tracking Closes #46 See merge request famedly/famedlysdk!179
This commit is contained in:
commit
166b1a9464
|
@ -26,6 +26,7 @@ library famedlysdk;
|
||||||
export 'package:famedlysdk/src/sync/room_update.dart';
|
export 'package:famedlysdk/src/sync/room_update.dart';
|
||||||
export 'package:famedlysdk/src/sync/event_update.dart';
|
export 'package:famedlysdk/src/sync/event_update.dart';
|
||||||
export 'package:famedlysdk/src/sync/user_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_exception.dart';
|
||||||
export 'package:famedlysdk/src/utils/matrix_file.dart';
|
export 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||||
export 'package:famedlysdk/src/utils/mx_content.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/presence.dart';
|
||||||
import 'package:famedlysdk/src/store_api.dart';
|
import 'package:famedlysdk/src/store_api.dart';
|
||||||
import 'package:famedlysdk/src/sync/user_update.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/matrix_file.dart';
|
||||||
import 'package:famedlysdk/src/utils/open_id_credentials.dart';
|
import 'package:famedlysdk/src/utils/open_id_credentials.dart';
|
||||||
import 'package:famedlysdk/src/utils/turn_server_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 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();
|
http.Client httpClient = http.Client();
|
||||||
|
|
||||||
|
@ -630,15 +637,16 @@ class Client {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Sends [LoginState.logged] to [onLoginStateChanged].
|
/// Sends [LoginState.logged] to [onLoginStateChanged].
|
||||||
void connect(
|
void connect({
|
||||||
{String newToken,
|
String newToken,
|
||||||
String newHomeserver,
|
String newHomeserver,
|
||||||
String newUserID,
|
String newUserID,
|
||||||
String newDeviceName,
|
String newDeviceName,
|
||||||
String newDeviceID,
|
String newDeviceID,
|
||||||
List<String> newMatrixVersions,
|
List<String> newMatrixVersions,
|
||||||
bool newLazyLoadMembers,
|
bool newLazyLoadMembers,
|
||||||
String newPrevBatch}) async {
|
String newPrevBatch,
|
||||||
|
}) async {
|
||||||
this._accessToken = newToken;
|
this._accessToken = newToken;
|
||||||
this._homeserver = newHomeserver;
|
this._homeserver = newHomeserver;
|
||||||
this._userID = newUserID;
|
this._userID = newUserID;
|
||||||
|
@ -650,6 +658,7 @@ class Client {
|
||||||
|
|
||||||
if (this.storeAPI != null) {
|
if (this.storeAPI != null) {
|
||||||
await this.storeAPI.storeClient();
|
await this.storeAPI.storeClient();
|
||||||
|
_userDeviceKeys = await this.storeAPI.getUserDeviceKeys();
|
||||||
if (this.store != null) {
|
if (this.store != null) {
|
||||||
this._rooms = await this.store.getRoomList(onlyLeft: false);
|
this._rooms = await this.store.getRoomList(onlyLeft: false);
|
||||||
this._sortRooms();
|
this._sortRooms();
|
||||||
|
@ -833,6 +842,7 @@ class Client {
|
||||||
_sortRooms();
|
_sortRooms();
|
||||||
}
|
}
|
||||||
this.prevBatch = syncResp["next_batch"];
|
this.prevBatch = syncResp["next_batch"];
|
||||||
|
unawaited(_updateUserDeviceKeys());
|
||||||
if (hash == _syncRequest.hashCode) unawaited(_sync());
|
if (hash == _syncRequest.hashCode) unawaited(_sync());
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
onError.add(exception);
|
onError.add(exception);
|
||||||
|
@ -866,9 +876,29 @@ class Client {
|
||||||
sync["to_device"]["events"] is List<dynamic>) {
|
sync["to_device"]["events"] is List<dynamic>) {
|
||||||
_handleGlobalEvents(sync["to_device"]["events"], "to_device");
|
_handleGlobalEvents(sync["to_device"]["events"], "to_device");
|
||||||
}
|
}
|
||||||
|
if (sync["device_lists"] is Map<String, dynamic>) {
|
||||||
|
_handleDeviceListsEvents(sync["device_lists"]);
|
||||||
|
}
|
||||||
onSync.add(sync);
|
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) {
|
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
||||||
rooms.forEach((String id, dynamic room) async {
|
rooms.forEach((String id, dynamic room) async {
|
||||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||||
|
@ -1011,6 +1041,13 @@ class Client {
|
||||||
|
|
||||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
||||||
if (event["type"] is String && event["content"] is Map<String, dynamic>) {
|
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(
|
EventUpdate update = EventUpdate(
|
||||||
eventType: event["type"],
|
eventType: event["type"],
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
|
@ -1162,4 +1199,65 @@ class Client {
|
||||||
);
|
);
|
||||||
return OpenIdCredentials.fromJson(response);
|
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;
|
return EventTypes.Sticker;
|
||||||
case "m.room.message":
|
case "m.room.message":
|
||||||
return EventTypes.Message;
|
return EventTypes.Message;
|
||||||
case "m.call.encrypted":
|
case "m.room.encrypted":
|
||||||
return EventTypes.Encrypted;
|
return EventTypes.Encrypted;
|
||||||
case "m.call.encryption":
|
case "m.room.encryption":
|
||||||
return EventTypes.Encryption;
|
return EventTypes.Encryption;
|
||||||
case "m.call.invite":
|
case "m.call.invite":
|
||||||
return EventTypes.CallInvite;
|
return EventTypes.CallInvite;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/client.dart';
|
import 'package:famedlysdk/src/client.dart';
|
||||||
import 'package:famedlysdk/src/event.dart';
|
import 'package:famedlysdk/src/event.dart';
|
||||||
import 'package:famedlysdk/src/room_account_data.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
|
/// Request the full list of participants from the server. The local list
|
||||||
/// from the store is not complete if the client uses lazy loading.
|
/// from the store is not complete if the client uses lazy loading.
|
||||||
Future<List<User>> requestParticipants() async {
|
Future<List<User>> requestParticipants() async {
|
||||||
|
if (participantListComplete) return getParticipants();
|
||||||
List<User> participants = [];
|
List<User> participants = [];
|
||||||
|
|
||||||
dynamic res = await client.jsonRequest(
|
dynamic res = await client.jsonRequest(
|
||||||
|
@ -768,12 +770,24 @@ class Room {
|
||||||
|
|
||||||
for (num i = 0; i < res["chunk"].length; i++) {
|
for (num i = 0; i < res["chunk"].length; i++) {
|
||||||
User newUser = Event.fromJson(res["chunk"][i], this).asUser;
|
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;
|
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
|
/// Returns the [User] object for the given [mxID] or requests it from
|
||||||
/// the homeserver and waits for a response.
|
/// the homeserver and waits for a response.
|
||||||
Future<User> getUserByMXID(String mxID) async {
|
Future<User> getUserByMXID(String mxID) async {
|
||||||
|
@ -1235,4 +1249,42 @@ class Room {
|
||||||
/// Whether the user has the permission to change the history visibility.
|
/// Whether the user has the permission to change the history visibility.
|
||||||
bool get canChangeHistoryVisibility =>
|
bool get canChangeHistoryVisibility =>
|
||||||
canSendEvent("m.room.history_visibility");
|
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 'dart:core';
|
||||||
import 'package:famedlysdk/src/account_data.dart';
|
import 'package:famedlysdk/src/account_data.dart';
|
||||||
import 'package:famedlysdk/src/presence.dart';
|
import 'package:famedlysdk/src/presence.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||||
import 'client.dart';
|
import 'client.dart';
|
||||||
import 'event.dart';
|
import 'event.dart';
|
||||||
import 'room.dart';
|
import 'room.dart';
|
||||||
|
@ -46,6 +47,10 @@ abstract class StoreAPI {
|
||||||
|
|
||||||
/// Clears all tables from the database.
|
/// Clears all tables from the database.
|
||||||
Future<void> clear();
|
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
|
/// 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
|
source: hosted
|
||||||
version: "0.6.1+1"
|
version: "0.6.1+1"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
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:
|
kernel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -365,13 +358,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.3"
|
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:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -10,10 +10,8 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
http: ^0.12.0+2
|
http: ^0.12.0+2
|
||||||
mime_type: ^0.2.4
|
mime_type: ^0.2.4
|
||||||
json_annotation: ^2.4.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
build_runner: ^1.5.2
|
build_runner: ^1.5.2
|
||||||
json_serializable: ^3.0.0
|
|
||||||
pedantic: ^1.5.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL
|
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.length, 1);
|
||||||
expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com");
|
expect(matrix.rooms[1].typingUsers[0].id, "@alice:example.com");
|
||||||
expect(matrix.rooms[1].roomAccountData.length, 3);
|
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||||
|
expect(matrix.rooms[1].encrypted, true);
|
||||||
|
expect(matrix.rooms[1].encryptionAlgorithm,
|
||||||
|
Client.supportedGroupEncryptionAlgorithms.first);
|
||||||
expect(
|
expect(
|
||||||
matrix.rooms[1].roomAccountData["m.receipt"]
|
matrix.rooms[1].roomAccountData["m.receipt"]
|
||||||
.content["@alice:example.com"]["ts"],
|
.content["@alice:example.com"]["ts"],
|
||||||
|
@ -151,11 +154,33 @@ void main() {
|
||||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||||
final List<User> contacts = await matrix.loadFamedlyContacts();
|
final List<User> contacts = await matrix.loadFamedlyContacts();
|
||||||
expect(contacts.length, 1);
|
expect(contacts.length, 1);
|
||||||
expect(contacts[0].senderId, "@alice:example.org");
|
expect(contacts[0].senderId, "@alice:example.com");
|
||||||
expect(
|
expect(
|
||||||
matrix.presences["@alice:example.com"].presence, PresenceType.online);
|
matrix.presences["@alice:example.com"].presence, PresenceType.online);
|
||||||
expect(presenceCounter, 1);
|
expect(presenceCounter, 1);
|
||||||
expect(accountDataCounter, 3);
|
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({
|
matrix.handleSync({
|
||||||
"rooms": {
|
"rooms": {
|
||||||
|
@ -184,6 +209,7 @@ void main() {
|
||||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}"),
|
||||||
null);
|
null);
|
||||||
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
final List<User> altContacts = await matrix.loadFamedlyContacts();
|
||||||
|
altContacts.forEach((u) => print(u.id));
|
||||||
expect(altContacts.length, 2);
|
expect(altContacts.length, 2);
|
||||||
expect(altContacts[0].senderId, "@alice:example.com");
|
expect(altContacts[0].senderId, "@alice:example.com");
|
||||||
});
|
});
|
||||||
|
@ -248,7 +274,7 @@ void main() {
|
||||||
|
|
||||||
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
List<EventUpdate> eventUpdateList = await eventUpdateListFuture;
|
||||||
|
|
||||||
expect(eventUpdateList.length, 12);
|
expect(eventUpdateList.length, 13);
|
||||||
|
|
||||||
expect(eventUpdateList[0].eventType, "m.room.member");
|
expect(eventUpdateList[0].eventType, "m.room.member");
|
||||||
expect(eventUpdateList[0].roomID, "!726s6s6q:example.com");
|
expect(eventUpdateList[0].roomID, "!726s6s6q:example.com");
|
||||||
|
@ -258,41 +284,45 @@ void main() {
|
||||||
expect(eventUpdateList[1].roomID, "!726s6s6q:example.com");
|
expect(eventUpdateList[1].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[1].type, "state");
|
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].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].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[3].type, "timeline");
|
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].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].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[5].type, "ephemeral");
|
expect(eventUpdateList[5].type, "ephemeral");
|
||||||
|
|
||||||
expect(eventUpdateList[6].eventType, "m.receipt");
|
expect(eventUpdateList[6].eventType, "m.receipt");
|
||||||
expect(eventUpdateList[6].roomID, "!726s6s6q:example.com");
|
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].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[7].type, "account_data");
|
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].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[8].type, "account_data");
|
expect(eventUpdateList[8].type, "account_data");
|
||||||
|
|
||||||
expect(eventUpdateList[9].eventType, "m.room.name");
|
expect(eventUpdateList[9].eventType, "org.example.custom.room.config");
|
||||||
expect(eventUpdateList[9].roomID, "!696r7674:example.com");
|
expect(eventUpdateList[9].roomID, "!726s6s6q:example.com");
|
||||||
expect(eventUpdateList[9].type, "invite_state");
|
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].roomID, "!696r7674:example.com");
|
||||||
expect(eventUpdateList[10].type, "invite_state");
|
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 {
|
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": "",
|
"state_key": "",
|
||||||
"origin_server_ts": 1417731086796,
|
"origin_server_ts": 1417731086796,
|
||||||
"event_id": "66697273743032:example.com"
|
"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": {
|
"timeline": {
|
||||||
|
@ -582,10 +590,10 @@ class FakeMatrixApi extends MockClient {
|
||||||
"type": "m.room.member",
|
"type": "m.room.member",
|
||||||
"event_id": "§143273582443PhrSn:example.org",
|
"event_id": "§143273582443PhrSn:example.org",
|
||||||
"room_id": "!636q39766251:example.com",
|
"room_id": "!636q39766251:example.com",
|
||||||
"sender": "@alice:example.org",
|
"sender": "@alice:example.com",
|
||||||
"origin_server_ts": 1432735824653,
|
"origin_server_ts": 1432735824653,
|
||||||
"unsigned": {"age": 1234},
|
"unsigned": {"age": 1234},
|
||||||
"state_key": "@alice:example.org"
|
"state_key": "@alice:example.com"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -760,6 +768,34 @@ class FakeMatrixApi extends MockClient {
|
||||||
{"available": true},
|
{"available": true},
|
||||||
},
|
},
|
||||||
"POST": {
|
"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": (var req) => {"user_id": "@testuser:example.com"},
|
||||||
"/client/r0/register?kind=user": (var req) =>
|
"/client/r0/register?kind=user": (var req) =>
|
||||||
{"user_id": "@testuser:example.com"},
|
{"user_id": "@testuser:example.com"},
|
||||||
|
|
|
@ -298,8 +298,8 @@ void main() {
|
||||||
content: {"displayname": "alice"},
|
content: {"displayname": "alice"},
|
||||||
stateKey: "@alice:test.abc"));
|
stateKey: "@alice:test.abc"));
|
||||||
final List<User> userList = room.getParticipants();
|
final List<User> userList = room.getParticipants();
|
||||||
expect(userList.length, 4);
|
expect(userList.length, 5);
|
||||||
expect(userList[3].displayName, "alice");
|
expect(userList[3].displayName, "Alice Margatroid");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("addToDirectChat", () async {
|
test("addToDirectChat", () async {
|
||||||
|
|
Loading…
Reference in a new issue