Merge branch 'soru/moor' into 'master'
Switch to moor Closes #52 See merge request famedly/famedlysdk!284
This commit is contained in:
commit
98d2f8d6bb
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -23,6 +23,7 @@ native/
|
|||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
|
|
|
@ -17,7 +17,7 @@ coverage:
|
|||
- curl https://storage.googleapis.com/dart-archive/channels/stable/release/2.7.2/linux_packages/dart_2.7.2-1_amd64.deb > dart.deb
|
||||
- apt install -y ./dart.deb
|
||||
- apt update
|
||||
- apt install -y chromium lcov libolm3
|
||||
- apt install -y chromium lcov libolm3 sqlite3 libsqlite3-dev
|
||||
- ln -s /usr/lib/dart/bin/pub /usr/bin/
|
||||
- useradd -m test
|
||||
- chown -R 'test:' '.'
|
||||
|
@ -33,7 +33,7 @@ coverage_without_olm:
|
|||
dependencies: []
|
||||
script:
|
||||
- apt update
|
||||
- apt install -y curl git
|
||||
- apt install -y curl git sqlite3 libsqlite3-dev
|
||||
- curl https://storage.googleapis.com/dart-archive/channels/stable/release/2.7.2/linux_packages/dart_2.7.2-1_amd64.deb > dart.deb
|
||||
- apt install -y ./dart.deb
|
||||
- ln -s /usr/lib/dart/bin/pub /usr/bin/
|
||||
|
|
|
@ -48,6 +48,6 @@ export 'package:famedlysdk/src/event.dart';
|
|||
export 'package:famedlysdk/src/presence.dart';
|
||||
export 'package:famedlysdk/src/room.dart';
|
||||
export 'package:famedlysdk/src/room_account_data.dart';
|
||||
export 'package:famedlysdk/src/store_api.dart';
|
||||
export 'package:famedlysdk/src/timeline.dart';
|
||||
export 'package:famedlysdk/src/user.dart';
|
||||
export 'package:famedlysdk/src/database/database.dart' show Database;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import './database/database.dart' show DbAccountData;
|
||||
|
||||
/// The global private data created by this user.
|
||||
class AccountData {
|
||||
|
@ -38,4 +39,10 @@ class AccountData {
|
|||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||
return AccountData(content: content, typeKey: jsonPayload['type']);
|
||||
}
|
||||
|
||||
/// Get account data from DbAccountData
|
||||
factory AccountData.fromDb(DbAccountData dbEntry) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return AccountData(content: content, typeKey: dbEntry.type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import 'package:famedlysdk/famedlysdk.dart';
|
|||
import 'package:famedlysdk/src/account_data.dart';
|
||||
import 'package:famedlysdk/src/presence.dart';
|
||||
import 'package:famedlysdk/src/room.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';
|
||||
|
@ -54,6 +53,7 @@ import 'sync/user_update.dart';
|
|||
import 'user.dart';
|
||||
import 'utils/matrix_exception.dart';
|
||||
import 'utils/profile.dart';
|
||||
import 'database/database.dart' show Database;
|
||||
import 'utils/pusher.dart';
|
||||
|
||||
typedef RoomSorter = int Function(Room a, Room b);
|
||||
|
@ -70,12 +70,12 @@ class Client {
|
|||
@deprecated
|
||||
Client get connection => this;
|
||||
|
||||
/// Optional persistent store for all data.
|
||||
ExtendedStoreAPI get store => (storeAPI?.extended ?? false) ? storeAPI : null;
|
||||
int _id;
|
||||
int get id => _id;
|
||||
|
||||
StoreAPI storeAPI;
|
||||
Database database;
|
||||
|
||||
Client(this.clientName, {this.debug = false, this.storeAPI}) {
|
||||
Client(this.clientName, {this.debug = false, this.database}) {
|
||||
onLoginStateChanged.stream.listen((loginState) {
|
||||
print('LoginState: ${loginState.toString()}');
|
||||
});
|
||||
|
@ -111,10 +111,6 @@ class Client {
|
|||
String get deviceName => _deviceName;
|
||||
String _deviceName;
|
||||
|
||||
/// Which version of the matrix specification does this server support?
|
||||
List<String> get matrixVersions => _matrixVersions;
|
||||
List<String> _matrixVersions;
|
||||
|
||||
/// Returns the current login state.
|
||||
bool isLogged() => accessToken != null;
|
||||
|
||||
|
@ -208,7 +204,7 @@ class Client {
|
|||
|
||||
/// Checks the supported versions of the Matrix protocol and the supported
|
||||
/// login types. Returns false if the server is not compatible with the
|
||||
/// client. Automatically sets [matrixVersions].
|
||||
/// client.
|
||||
/// Throws FormatException, TimeoutException and MatrixException on error.
|
||||
Future<bool> checkServer(serverUrl) async {
|
||||
try {
|
||||
|
@ -226,8 +222,6 @@ class Client {
|
|||
}
|
||||
}
|
||||
|
||||
_matrixVersions = versions;
|
||||
|
||||
final loginResp =
|
||||
await jsonRequest(type: HTTPType.GET, action: '/client/r0/login');
|
||||
|
||||
|
@ -243,7 +237,7 @@ class Client {
|
|||
}
|
||||
return true;
|
||||
} catch (_) {
|
||||
_homeserver = _matrixVersions = null;
|
||||
_homeserver = null;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
@ -292,8 +286,7 @@ class Client {
|
|||
newUserID: response['user_id'],
|
||||
newHomeserver: homeserver,
|
||||
newDeviceName: initialDeviceDisplayName ?? '',
|
||||
newDeviceID: response['device_id'],
|
||||
newMatrixVersions: matrixVersions);
|
||||
newDeviceID: response['device_id']);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
@ -334,7 +327,6 @@ class Client {
|
|||
newHomeserver: homeserver,
|
||||
newDeviceName: initialDeviceDisplayName ?? '',
|
||||
newDeviceID: loginResp['device_id'],
|
||||
newMatrixVersions: matrixVersions,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
@ -676,20 +668,39 @@ class Client {
|
|||
String newUserID,
|
||||
String newDeviceName,
|
||||
String newDeviceID,
|
||||
List<String> newMatrixVersions,
|
||||
String newPrevBatch,
|
||||
String newOlmAccount,
|
||||
}) async {
|
||||
_accessToken = newToken;
|
||||
_homeserver = newHomeserver;
|
||||
_userID = newUserID;
|
||||
_deviceID = newDeviceID;
|
||||
_deviceName = newDeviceName;
|
||||
_matrixVersions = newMatrixVersions;
|
||||
prevBatch = newPrevBatch;
|
||||
String olmAccount;
|
||||
if (database != null) {
|
||||
final account = await database.getClient(clientName);
|
||||
if (account != null) {
|
||||
_id = account.clientId;
|
||||
_homeserver = account.homeserverUrl;
|
||||
_accessToken = account.token;
|
||||
_userID = account.userId;
|
||||
_deviceID = account.deviceId;
|
||||
_deviceName = account.deviceName;
|
||||
prevBatch = account.prevBatch;
|
||||
olmAccount = account.olmAccount;
|
||||
}
|
||||
}
|
||||
_accessToken = newToken ?? _accessToken;
|
||||
_homeserver = newHomeserver ?? _homeserver;
|
||||
_userID = newUserID ?? _userID;
|
||||
_deviceID = newDeviceID ?? _deviceID;
|
||||
_deviceName = newDeviceName ?? _deviceName;
|
||||
prevBatch = newPrevBatch ?? prevBatch;
|
||||
olmAccount = newOlmAccount ?? olmAccount;
|
||||
|
||||
if (_accessToken == null || _homeserver == null || _userID == null) {
|
||||
// we aren't logged in
|
||||
onLoginStateChanged.add(LoginState.logged);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to create a new olm account or restore a previous one.
|
||||
if (newOlmAccount == null) {
|
||||
if (olmAccount == null) {
|
||||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
|
@ -704,39 +715,30 @@ class Client {
|
|||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
_olmAccount.unpickle(userID, newOlmAccount);
|
||||
_olmAccount.unpickle(userID, olmAccount);
|
||||
} catch (_) {
|
||||
_olmAccount = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (storeAPI != null) {
|
||||
await storeAPI.storeClient();
|
||||
_userDeviceKeys = await storeAPI.getUserDeviceKeys();
|
||||
final String olmSessionPickleString =
|
||||
await storeAPI.getItem('/clients/$userID/olm-sessions');
|
||||
if (olmSessionPickleString != null) {
|
||||
final Map<String, dynamic> pickleMap =
|
||||
json.decode(olmSessionPickleString);
|
||||
for (var entry in pickleMap.entries) {
|
||||
for (String pickle in entry.value) {
|
||||
_olmSessions[entry.key] = [];
|
||||
try {
|
||||
var session = olm.Session();
|
||||
session.unpickle(userID, pickle);
|
||||
_olmSessions[entry.key].add(session);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle olm session: ' + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (store != null) {
|
||||
_rooms = await store.getRoomList(onlyLeft: false);
|
||||
_sortRooms();
|
||||
accountData = await store.getAccountData();
|
||||
presences = await store.getPresences();
|
||||
if (database != null) {
|
||||
if (id != null) {
|
||||
await database.updateClient(
|
||||
_homeserver, _accessToken, _userID, _deviceID,
|
||||
_deviceName, prevBatch, pickledOlmAccount, id,
|
||||
);
|
||||
} else {
|
||||
_id = await database.insertClient(
|
||||
clientName, _homeserver, _accessToken, _userID, _deviceID,
|
||||
_deviceName, prevBatch, pickledOlmAccount,
|
||||
);
|
||||
}
|
||||
_userDeviceKeys = await database.getUserDeviceKeys(id);
|
||||
_olmSessions = await database.getOlmSessions(id, _userID);
|
||||
_rooms = await database.getRoomList(this, onlyLeft: false);
|
||||
_sortRooms();
|
||||
accountData = await database.getAccountData(id);
|
||||
presences = await database.getPresences(id);
|
||||
}
|
||||
|
||||
_userEventSub ??= onUserEvent.stream.listen(handleUserUpdate);
|
||||
|
@ -755,14 +757,14 @@ class Client {
|
|||
});
|
||||
rooms.forEach((Room room) {
|
||||
room.clearOutboundGroupSession(wipe: true);
|
||||
room.sessionKeys.values.forEach((SessionKey sessionKey) {
|
||||
room.inboundGroupSessions.values.forEach((SessionKey sessionKey) {
|
||||
sessionKey.inboundGroupSession?.free();
|
||||
});
|
||||
});
|
||||
_olmAccount?.free();
|
||||
storeAPI?.clear();
|
||||
_accessToken = _homeserver =
|
||||
_userID = _deviceID = _deviceName = _matrixVersions = prevBatch = null;
|
||||
database?.clear(id);
|
||||
_id = _accessToken = _homeserver =
|
||||
_userID = _deviceID = _deviceName = prevBatch = null;
|
||||
_rooms = [];
|
||||
onLoginStateChanged.add(LoginState.loggedOut);
|
||||
}
|
||||
|
@ -858,6 +860,7 @@ class Client {
|
|||
var exception = MatrixException(resp);
|
||||
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
||||
// The token is no longer valid. Need to sign off....
|
||||
// TODO: add a way to export keys prior logout?
|
||||
onError.add(exception);
|
||||
clear();
|
||||
}
|
||||
|
@ -926,14 +929,15 @@ class Client {
|
|||
final syncResp = await _syncRequest;
|
||||
if (hash != _syncRequest.hashCode) return;
|
||||
_timeoutFactor = 1;
|
||||
if (store != null) {
|
||||
await store.transaction(() {
|
||||
handleSync(syncResp);
|
||||
store.storePrevBatch(syncResp['next_batch']);
|
||||
});
|
||||
} else {
|
||||
await handleSync(syncResp);
|
||||
}
|
||||
final futures = handleSync(syncResp);
|
||||
await database?.transaction(() async {
|
||||
for (final f in futures) {
|
||||
await f();
|
||||
}
|
||||
if (prevBatch != syncResp['next_batch']) {
|
||||
await database.storePrevBatch(syncResp['next_batch'], id);
|
||||
}
|
||||
});
|
||||
if (prevBatch == null) {
|
||||
onFirstSync.add(true);
|
||||
prevBatch = syncResp['next_batch'];
|
||||
|
@ -946,37 +950,39 @@ class Client {
|
|||
onError.add(exception);
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
} catch (exception) {
|
||||
print('Error during processing events: ' + exception.toString());
|
||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this method only for testing utilities!
|
||||
void handleSync(dynamic sync) {
|
||||
List<Future<dynamic> Function()> handleSync(dynamic sync) {
|
||||
final dbActions = <Future<dynamic> Function()>[];
|
||||
if (sync['to_device'] is Map<String, dynamic> &&
|
||||
sync['to_device']['events'] is List<dynamic>) {
|
||||
_handleToDeviceEvents(sync['to_device']['events']);
|
||||
}
|
||||
if (sync['rooms'] is Map<String, dynamic>) {
|
||||
if (sync['rooms']['join'] is Map<String, dynamic>) {
|
||||
_handleRooms(sync['rooms']['join'], Membership.join);
|
||||
_handleRooms(sync['rooms']['join'], Membership.join, dbActions);
|
||||
}
|
||||
if (sync['rooms']['invite'] is Map<String, dynamic>) {
|
||||
_handleRooms(sync['rooms']['invite'], Membership.invite);
|
||||
_handleRooms(sync['rooms']['invite'], Membership.invite, dbActions);
|
||||
}
|
||||
if (sync['rooms']['leave'] is Map<String, dynamic>) {
|
||||
_handleRooms(sync['rooms']['leave'], Membership.leave);
|
||||
_handleRooms(sync['rooms']['leave'], Membership.leave, dbActions);
|
||||
}
|
||||
}
|
||||
if (sync['presence'] is Map<String, dynamic> &&
|
||||
sync['presence']['events'] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync['presence']['events'], 'presence');
|
||||
_handleGlobalEvents(sync['presence']['events'], 'presence', dbActions);
|
||||
}
|
||||
if (sync['account_data'] is Map<String, dynamic> &&
|
||||
sync['account_data']['events'] is List<dynamic>) {
|
||||
_handleGlobalEvents(sync['account_data']['events'], 'account_data');
|
||||
_handleGlobalEvents(sync['account_data']['events'], 'account_data', dbActions);
|
||||
}
|
||||
if (sync['device_lists'] is Map<String, dynamic>) {
|
||||
_handleDeviceListsEvents(sync['device_lists']);
|
||||
_handleDeviceListsEvents(sync['device_lists'], dbActions);
|
||||
}
|
||||
if (sync['device_one_time_keys_count'] is Map<String, dynamic>) {
|
||||
_handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']);
|
||||
|
@ -988,6 +994,7 @@ class Client {
|
|||
);
|
||||
}
|
||||
onSync.add(sync);
|
||||
return dbActions;
|
||||
}
|
||||
|
||||
void _handleDeviceOneTimeKeysCount(
|
||||
|
@ -1004,11 +1011,14 @@ class Client {
|
|||
}
|
||||
}
|
||||
|
||||
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists) {
|
||||
void _handleDeviceListsEvents(Map<String, dynamic> deviceLists, List<Future<dynamic> Function()> dbActions) {
|
||||
if (deviceLists['changed'] is List) {
|
||||
for (final userId in deviceLists['changed']) {
|
||||
if (_userDeviceKeys.containsKey(userId)) {
|
||||
_userDeviceKeys[userId].outdated = true;
|
||||
if (database != null) {
|
||||
dbActions.add(() => database.storeUserDeviceKeysInfo(id, userId, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final userId in deviceLists['left']) {
|
||||
|
@ -1046,8 +1056,8 @@ class Client {
|
|||
}
|
||||
}
|
||||
|
||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
||||
rooms.forEach((String id, dynamic room) async {
|
||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership, List<Future<dynamic> Function()> dbActions) {
|
||||
rooms.forEach((String id, dynamic room) {
|
||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||
num highlight_count = 0;
|
||||
num notification_count = 0;
|
||||
|
@ -1089,40 +1099,55 @@ class Client {
|
|||
summary: summary,
|
||||
);
|
||||
_updateRoomsByRoomUpdate(update);
|
||||
unawaited(store?.storeRoomUpdate(update));
|
||||
final roomObj = getRoomById(id);
|
||||
if (limitedTimeline && roomObj != null) {
|
||||
roomObj.resetSortOrder();
|
||||
}
|
||||
if (database != null) {
|
||||
dbActions.add(() => database.storeRoomUpdate(this.id, update, getRoomById(id)));
|
||||
}
|
||||
onRoomUpdate.add(update);
|
||||
|
||||
var handledEvents = false;
|
||||
/// Handle now all room events and save them in the database
|
||||
if (room['state'] is Map<String, dynamic> &&
|
||||
room['state']['events'] is List<dynamic>) {
|
||||
_handleRoomEvents(id, room['state']['events'], 'state');
|
||||
room['state']['events'] is List<dynamic> &&
|
||||
room['state']['events'].isNotEmpty) {
|
||||
_handleRoomEvents(id, room['state']['events'], 'state', dbActions);
|
||||
handledEvents = true;
|
||||
}
|
||||
|
||||
if (room['invite_state'] is Map<String, dynamic> &&
|
||||
room['invite_state']['events'] is List<dynamic>) {
|
||||
_handleRoomEvents(id, room['invite_state']['events'], 'invite_state');
|
||||
_handleRoomEvents(id, room['invite_state']['events'], 'invite_state', dbActions);
|
||||
}
|
||||
|
||||
if (room['timeline'] is Map<String, dynamic> &&
|
||||
room['timeline']['events'] is List<dynamic>) {
|
||||
_handleRoomEvents(id, room['timeline']['events'], 'timeline');
|
||||
room['timeline']['events'] is List<dynamic> &&
|
||||
room['timeline']['events'].isNotEmpty) {
|
||||
_handleRoomEvents(id, room['timeline']['events'], 'timeline', dbActions);
|
||||
handledEvents = true;
|
||||
}
|
||||
|
||||
if (room['ephemeral'] is Map<String, dynamic> &&
|
||||
room['ephemeral']['events'] is List<dynamic>) {
|
||||
_handleEphemerals(id, room['ephemeral']['events']);
|
||||
_handleEphemerals(id, room['ephemeral']['events'], dbActions);
|
||||
}
|
||||
|
||||
if (room['account_data'] is Map<String, dynamic> &&
|
||||
room['account_data']['events'] is List<dynamic>) {
|
||||
_handleRoomEvents(id, room['account_data']['events'], 'account_data');
|
||||
_handleRoomEvents(id, room['account_data']['events'], 'account_data', dbActions);
|
||||
}
|
||||
|
||||
if (handledEvents && database != null && roomObj != null) {
|
||||
dbActions.add(() => roomObj.updateSortOrder());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _handleEphemerals(String id, List<dynamic> events) {
|
||||
void _handleEphemerals(String id, List<dynamic> events, List<Future<dynamic> Function()> dbActions) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], id, 'ephemeral');
|
||||
_handleEvent(events[i], id, 'ephemeral', dbActions);
|
||||
|
||||
// Receipt events are deltas between two states. We will create a
|
||||
// fake room account data event for this and store the difference
|
||||
|
@ -1142,12 +1167,12 @@ class Client {
|
|||
final mxid = userTimestampMapEntry.key;
|
||||
|
||||
// Remove previous receipt event from this user
|
||||
for (var entry in receiptStateContent.entries) {
|
||||
if (entry.value['m.read'] is Map<String, dynamic> &&
|
||||
entry.value['m.read'].containsKey(mxid)) {
|
||||
entry.value['m.read'].remove(mxid);
|
||||
break;
|
||||
}
|
||||
if (
|
||||
receiptStateContent[eventID] is Map<String, dynamic> &&
|
||||
receiptStateContent[eventID]['m.read'] is Map<String, dynamic> &&
|
||||
receiptStateContent[eventID]['m.read'].containsKey(mxid)
|
||||
) {
|
||||
receiptStateContent[eventID]['m.read'].remove(mxid);
|
||||
}
|
||||
if (userTimestampMap[mxid] is Map<String, dynamic> &&
|
||||
userTimestampMap[mxid].containsKey('ts')) {
|
||||
|
@ -1160,18 +1185,18 @@ class Client {
|
|||
}
|
||||
}
|
||||
events[i]['content'] = receiptStateContent;
|
||||
_handleEvent(events[i], id, 'account_data');
|
||||
_handleEvent(events[i], id, 'account_data', dbActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRoomEvents(String chat_id, List<dynamic> events, String type) {
|
||||
void _handleRoomEvents(String chat_id, List<dynamic> events, String type, List<Future<dynamic> Function()> dbActions) {
|
||||
for (num i = 0; i < events.length; i++) {
|
||||
_handleEvent(events[i], chat_id, type);
|
||||
_handleEvent(events[i], chat_id, type, dbActions);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleGlobalEvents(List<dynamic> events, String type) {
|
||||
void _handleGlobalEvents(List<dynamic> events, String type, List<Future<dynamic> Function()> dbActions) {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i]['type'] is String &&
|
||||
events[i]['content'] is Map<String, dynamic>) {
|
||||
|
@ -1180,42 +1205,51 @@ class Client {
|
|||
type: type,
|
||||
content: events[i],
|
||||
);
|
||||
store?.storeUserEventUpdate(update);
|
||||
if (database != null) {
|
||||
dbActions.add(() => database.storeUserEventUpdate(id, update));
|
||||
}
|
||||
onUserEvent.add(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type) {
|
||||
void _handleEvent(Map<String, dynamic> event, String roomID, String type, List<Future<dynamic> Function()> dbActions) {
|
||||
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) {
|
||||
final room = getRoomById(roomID);
|
||||
if (room == null || (event['type'] == 'm.room.encryption' &&
|
||||
room.encrypted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ephemeral events aren't persisted and don't need a sort order - they are
|
||||
// expected to be processed as soon as they come in
|
||||
final sortOrder = type != 'ephemeral' ? room.newSortOrder : 0.0;
|
||||
var update = EventUpdate(
|
||||
eventType: event['type'],
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: event,
|
||||
sortOrder: sortOrder,
|
||||
);
|
||||
if (event['type'] == 'm.room.encrypted') {
|
||||
update = update.decrypt(getRoomById(update.roomID));
|
||||
update = update.decrypt(room);
|
||||
}
|
||||
if (type != 'ephemeral' && database != null) {
|
||||
dbActions.add(() => database.storeEventUpdate(id, update));
|
||||
}
|
||||
store?.storeEventUpdate(update);
|
||||
_updateRoomsByEventUpdate(update);
|
||||
onEvent.add(update);
|
||||
|
||||
if (event['type'] == 'm.call.invite') {
|
||||
onCallInvite.add(Event.fromJson(event, getRoomById(roomID)));
|
||||
onCallInvite.add(Event.fromJson(event, room, sortOrder));
|
||||
} else if (event['type'] == 'm.call.hangup') {
|
||||
onCallHangup.add(Event.fromJson(event, getRoomById(roomID)));
|
||||
onCallHangup.add(Event.fromJson(event, room, sortOrder));
|
||||
} else if (event['type'] == 'm.call.answer') {
|
||||
onCallAnswer.add(Event.fromJson(event, getRoomById(roomID)));
|
||||
onCallAnswer.add(Event.fromJson(event, room, sortOrder));
|
||||
} else if (event['type'] == 'm.call.candidates') {
|
||||
onCallCandidates.add(Event.fromJson(event, getRoomById(roomID)));
|
||||
onCallCandidates.add(Event.fromJson(event, room, sortOrder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1294,7 +1328,7 @@ class Client {
|
|||
if (eventUpdate.type == 'timeline' ||
|
||||
eventUpdate.type == 'state' ||
|
||||
eventUpdate.type == 'invite_state') {
|
||||
var stateEvent = Event.fromJson(eventUpdate.content, rooms[j]);
|
||||
var stateEvent = Event.fromJson(eventUpdate.content, rooms[j], eventUpdate.sortOrder);
|
||||
if (stateEvent.type == EventTypes.Redaction) {
|
||||
final String redacts = eventUpdate.content['redacts'];
|
||||
rooms[j].states.states.forEach(
|
||||
|
@ -1337,6 +1371,7 @@ class Client {
|
|||
var room = getRoomById(roomId);
|
||||
if (room == null && addToPendingIfNotFound) {
|
||||
_pendingToDeviceEvents.add(toDeviceEvent);
|
||||
break;
|
||||
}
|
||||
final String sessionId = toDeviceEvent.content['session_id'];
|
||||
if (toDeviceEvent.type == 'm.room_key' &&
|
||||
|
@ -1349,7 +1384,7 @@ class Client {
|
|||
.deviceKeys[toDeviceEvent.content['requesting_device_id']]
|
||||
.ed25519Key;
|
||||
}
|
||||
room.setSessionKey(
|
||||
room.setInboundGroupSession(
|
||||
sessionId,
|
||||
toDeviceEvent.content,
|
||||
forwarded: toDeviceEvent.type == 'm.forwarded_room_key',
|
||||
|
@ -1379,7 +1414,7 @@ class Client {
|
|||
.containsKey(toDeviceEvent.content['requesting_device_id'])) {
|
||||
deviceKeys = userDeviceKeys[toDeviceEvent.sender]
|
||||
.deviceKeys[toDeviceEvent.content['requesting_device_id']];
|
||||
if (room.sessionKeys.containsKey(sessionId)) {
|
||||
if (room.inboundGroupSessions.containsKey(sessionId)) {
|
||||
final roomKeyRequest =
|
||||
RoomKeyRequest.fromToDeviceEvent(toDeviceEvent, this);
|
||||
if (deviceKeys.userId == userID &&
|
||||
|
@ -1446,6 +1481,7 @@ class Client {
|
|||
Future<void> _updateUserDeviceKeys() async {
|
||||
try {
|
||||
if (!isLogged()) return;
|
||||
final dbActions = <Future<dynamic> Function()>[];
|
||||
var trackedUserIds = await _getUserIdsInEncryptedRooms();
|
||||
trackedUserIds.add(userID);
|
||||
|
||||
|
@ -1481,6 +1517,7 @@ class Client {
|
|||
final String deviceId = rawDeviceKeyEntry.key;
|
||||
|
||||
// Set the new device key for this device
|
||||
|
||||
if (!oldKeys.containsKey(deviceId)) {
|
||||
_userDeviceKeys[userId].deviceKeys[deviceId] =
|
||||
DeviceKeys.fromJson(rawDeviceKeyEntry.value);
|
||||
|
@ -1493,11 +1530,34 @@ class Client {
|
|||
} else {
|
||||
_userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId];
|
||||
}
|
||||
if (database != null) {
|
||||
dbActions.add(() => database.storeUserDeviceKey(id, userId, deviceId,
|
||||
json.encode(_userDeviceKeys[userId].deviceKeys[deviceId].toJson()),
|
||||
_userDeviceKeys[userId].deviceKeys[deviceId].verified,
|
||||
_userDeviceKeys[userId].deviceKeys[deviceId].blocked,
|
||||
));
|
||||
}
|
||||
}
|
||||
if (database != null) {
|
||||
for (final oldDeviceKeyEntry in oldKeys.entries) {
|
||||
final deviceId = oldDeviceKeyEntry.key;
|
||||
if (!_userDeviceKeys[userId].deviceKeys.containsKey(deviceId)) {
|
||||
// we need to remove an old key
|
||||
dbActions.add(() => database.removeUserDeviceKey(id, userId, deviceId));
|
||||
}
|
||||
}
|
||||
}
|
||||
_userDeviceKeys[userId].outdated = false;
|
||||
if (database != null) {
|
||||
dbActions.add(() => database.storeUserDeviceKeysInfo(id, userId, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
await storeAPI?.storeUserDeviceKeys(userDeviceKeys);
|
||||
await database?.transaction(() async {
|
||||
for (final f in dbActions) {
|
||||
await f();
|
||||
}
|
||||
});
|
||||
rooms.forEach((Room room) {
|
||||
if (room.encrypted) {
|
||||
room.clearOutboundGroupSession();
|
||||
|
@ -1616,7 +1676,7 @@ class Client {
|
|||
oneTimeKeysCount) {
|
||||
return false;
|
||||
}
|
||||
await storeAPI?.storeClient();
|
||||
await database?.updateClientKeys(pickledOlmAccount, id);
|
||||
lastTimeKeysUploaded = DateTime.now();
|
||||
return true;
|
||||
}
|
||||
|
@ -1668,7 +1728,7 @@ class Client {
|
|||
var newSession = olm.Session();
|
||||
newSession.create_inbound_from(_olmAccount, senderKey, body);
|
||||
_olmAccount.remove_one_time_keys(newSession);
|
||||
storeAPI?.storeClient();
|
||||
database?.updateClientKeys(pickledOlmAccount, id);
|
||||
plaintext = newSession.decrypt(type, body);
|
||||
storeOlmSession(senderKey, newSession);
|
||||
}
|
||||
|
@ -1695,29 +1755,24 @@ class Client {
|
|||
|
||||
/// A map from Curve25519 identity keys to existing olm sessions.
|
||||
Map<String, List<olm.Session>> get olmSessions => _olmSessions;
|
||||
final Map<String, List<olm.Session>> _olmSessions = {};
|
||||
Map<String, List<olm.Session>> _olmSessions = {};
|
||||
|
||||
void storeOlmSession(String curve25519IdentityKey, olm.Session session) {
|
||||
if (!_olmSessions.containsKey(curve25519IdentityKey)) {
|
||||
_olmSessions[curve25519IdentityKey] = [];
|
||||
}
|
||||
if (_olmSessions[curve25519IdentityKey]
|
||||
.indexWhere((s) => s.session_id() == session.session_id()) ==
|
||||
final ix = _olmSessions[curve25519IdentityKey]
|
||||
.indexWhere((s) => s.session_id() == session.session_id());
|
||||
if (ix ==
|
||||
-1) {
|
||||
// add a new session
|
||||
_olmSessions[curve25519IdentityKey].add(session);
|
||||
} else {
|
||||
// update an existing session
|
||||
_olmSessions[curve25519IdentityKey][ix] = session;
|
||||
}
|
||||
var pickleMap = <String, List<String>>{};
|
||||
for (var entry in olmSessions.entries) {
|
||||
pickleMap[entry.key] = [];
|
||||
for (var session in entry.value) {
|
||||
try {
|
||||
pickleMap[entry.key].add(session.pickle(userID));
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not pickle olm session: ' + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
storeAPI?.setItem('/clients/$userID/olm-sessions', json.encode(pickleMap));
|
||||
final pickle = session.pickle(userID);
|
||||
database?.storeOlmSession(id, curve25519IdentityKey, session.session_id(), pickle);
|
||||
}
|
||||
|
||||
/// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send
|
||||
|
|
368
lib/src/database/database.dart
Normal file
368
lib/src/database/database.dart
Normal file
|
@ -0,0 +1,368 @@
|
|||
import 'package:moor/moor.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart' as sdk;
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@UseMoor(
|
||||
include: {'database.moor'},
|
||||
)
|
||||
class Database extends _$Database {
|
||||
Database(QueryExecutor e) : super(e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 2;
|
||||
|
||||
int get maxFileSize => 1 * 1024 * 1024;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
onCreate: (Migrator m) {
|
||||
return m.createAll();
|
||||
},
|
||||
onUpgrade: (Migrator m, int from, int to) async {
|
||||
// this appears to be only called once, so multiple consecutive upgrades have to be handled appropriately in here
|
||||
if (from == 1) {
|
||||
await m.createIndex(userDeviceKeysIndex);
|
||||
await m.createIndex(userDeviceKeysKeyIndex);
|
||||
await m.createIndex(olmSessionsIndex);
|
||||
await m.createIndex(outboundGroupSessionsIndex);
|
||||
await m.createIndex(inboundGroupSessionsIndex);
|
||||
await m.createIndex(roomsIndex);
|
||||
await m.createIndex(eventsIndex);
|
||||
await m.createIndex(roomStatesIndex);
|
||||
await m.createIndex(accountDataIndex);
|
||||
await m.createIndex(roomAccountDataIndex);
|
||||
await m.createIndex(presencesIndex);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Future<DbClient> getClient(String name) async {
|
||||
final res = await dbGetClient(name).get();
|
||||
if (res.isEmpty) return null;
|
||||
return res.first;
|
||||
}
|
||||
|
||||
Future<Map<String, sdk.DeviceKeysList>> getUserDeviceKeys(int clientId) async {
|
||||
final deviceKeys = await getAllUserDeviceKeys(clientId).get();
|
||||
if (deviceKeys.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
final deviceKeysKeys = await getAllUserDeviceKeysKeys(clientId).get();
|
||||
final res = <String, sdk.DeviceKeysList>{};
|
||||
for (final entry in deviceKeys) {
|
||||
res[entry.userId] = sdk.DeviceKeysList.fromDb(entry, deviceKeysKeys.where((k) => k.userId == entry.userId).toList());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<Map<String, List<olm.Session>>> getOlmSessions(int clientId, String userId) async {
|
||||
final raw = await getAllOlmSessions(clientId).get();
|
||||
if (raw.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
final res = <String, List<olm.Session>>{};
|
||||
for (final row in raw) {
|
||||
if (!res.containsKey(row.identityKey)) {
|
||||
res[row.identityKey] = [];
|
||||
}
|
||||
try {
|
||||
var session = olm.Session();
|
||||
session.unpickle(userId, row.pickle);
|
||||
res[row.identityKey].add(session);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle olm session: ' + e.toString());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<DbOutboundGroupSession> getDbOutboundGroupSession(int clientId, String roomId) async {
|
||||
final res = await dbGetOutboundGroupSession(clientId, roomId).get();
|
||||
if (res.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return res.first;
|
||||
}
|
||||
|
||||
Future<List<DbInboundGroupSession>> getDbInboundGroupSessions(int clientId, String roomId) async {
|
||||
return await dbGetInboundGroupSessionKeys(clientId, roomId).get();
|
||||
}
|
||||
|
||||
Future<List<sdk.Room>> getRoomList(sdk.Client client, {bool onlyLeft = false}) async {
|
||||
final res = await (select(rooms)..where((t) => onlyLeft
|
||||
? t.membership.equals('leave')
|
||||
: t.membership.equals('leave').not())).get();
|
||||
final resStates = await getAllRoomStates(client.id).get();
|
||||
final resAccountData = await getAllRoomAccountData(client.id).get();
|
||||
final resOutboundGroupSessions = await getAllOutboundGroupSessions(client.id).get();
|
||||
final resInboundGroupSessions = await getAllInboundGroupSessions(client.id).get();
|
||||
final roomList = <sdk.Room>[];
|
||||
for (final r in res) {
|
||||
final outboundGroupSession = resOutboundGroupSessions.where((rs) => rs.roomId == r.roomId);
|
||||
final room = await sdk.Room.getRoomFromTableRow(
|
||||
r,
|
||||
client,
|
||||
states: resStates.where((rs) => rs.roomId == r.roomId),
|
||||
roomAccountData: resAccountData.where((rs) => rs.roomId == r.roomId),
|
||||
outboundGroupSession: outboundGroupSession.isEmpty ? false : outboundGroupSession.first,
|
||||
inboundGroupSessions: resInboundGroupSessions.where((rs) => rs.roomId == r.roomId),
|
||||
);
|
||||
roomList.add(room);
|
||||
}
|
||||
return roomList;
|
||||
}
|
||||
|
||||
Future<Map<String, sdk.AccountData>> getAccountData(int clientId) async {
|
||||
final newAccountData = <String, sdk.AccountData>{};
|
||||
final rawAccountData = await getAllAccountData(clientId).get();
|
||||
for (final d in rawAccountData) {
|
||||
newAccountData[d.type] = sdk.AccountData.fromDb(d);
|
||||
}
|
||||
return newAccountData;
|
||||
}
|
||||
|
||||
Future<Map<String, sdk.Presence>> getPresences(int clientId) async {
|
||||
final newPresences = <String, sdk.Presence>{};
|
||||
final rawPresences = await getAllPresences(clientId).get();
|
||||
for (final d in rawPresences) {
|
||||
newPresences[d.sender] = sdk.Presence.fromDb(d);
|
||||
}
|
||||
return newPresences;
|
||||
}
|
||||
|
||||
/// Stores a RoomUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
final Set<String> _ensuredRooms = {};
|
||||
Future<void> storeRoomUpdate(int clientId, sdk.RoomUpdate roomUpdate, [sdk.Room oldRoom]) async {
|
||||
final setKey = '${clientId};${roomUpdate.id}';
|
||||
if (roomUpdate.membership != sdk.Membership.leave) {
|
||||
if (!_ensuredRooms.contains(setKey)) {
|
||||
await ensureRoomExists(clientId, roomUpdate.id, roomUpdate.membership.toString().split('.').last);
|
||||
_ensuredRooms.add(setKey);
|
||||
}
|
||||
} else {
|
||||
_ensuredRooms.remove(setKey);
|
||||
await removeRoom(clientId, roomUpdate.id);
|
||||
return;
|
||||
}
|
||||
|
||||
var doUpdate = oldRoom == null;
|
||||
if (!doUpdate) {
|
||||
doUpdate = roomUpdate.highlight_count != oldRoom.highlightCount ||
|
||||
roomUpdate.notification_count != oldRoom.notificationCount ||
|
||||
roomUpdate.membership.toString().split('.').last != oldRoom.membership.toString().split('.').last ||
|
||||
(roomUpdate.summary?.mJoinedMemberCount != null &&
|
||||
roomUpdate.summary.mJoinedMemberCount != oldRoom.mInvitedMemberCount) ||
|
||||
(roomUpdate.summary?.mInvitedMemberCount != null &&
|
||||
roomUpdate.summary.mJoinedMemberCount != oldRoom.mJoinedMemberCount) ||
|
||||
(roomUpdate.summary?.mHeroes != null &&
|
||||
roomUpdate.summary.mHeroes.join(',') != oldRoom.mHeroes.join(','));
|
||||
}
|
||||
|
||||
if (doUpdate) {
|
||||
await (update(rooms)..where((r) => r.roomId.equals(roomUpdate.id) & r.clientId.equals(clientId))).write(RoomsCompanion(
|
||||
highlightCount: Value(roomUpdate.highlight_count),
|
||||
notificationCount: Value(roomUpdate.notification_count),
|
||||
membership: Value(roomUpdate.membership.toString().split('.').last),
|
||||
joinedMemberCount: roomUpdate.summary?.mJoinedMemberCount != null ? Value(roomUpdate.summary.mJoinedMemberCount) : Value.absent(),
|
||||
invitedMemberCount: roomUpdate.summary?.mInvitedMemberCount != null ? Value(roomUpdate.summary.mInvitedMemberCount) : Value.absent(),
|
||||
heroes: roomUpdate.summary?.mHeroes != null ? Value(roomUpdate.summary.mHeroes.join(',')) : Value.absent(),
|
||||
));
|
||||
}
|
||||
|
||||
// Is the timeline limited? Then all previous messages should be
|
||||
// removed from the database!
|
||||
if (roomUpdate.limitedTimeline) {
|
||||
await removeRoomEvents(clientId, roomUpdate.id);
|
||||
await updateRoomSortOrder(0.0, 0.0, clientId, roomUpdate.id);
|
||||
await setRoomPrevBatch(roomUpdate.prev_batch, clientId, roomUpdate.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores an UserUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeUserEventUpdate(int clientId, sdk.UserUpdate userUpdate) async {
|
||||
if (userUpdate.type == 'account_data') {
|
||||
await storeAccountData(clientId, userUpdate.eventType, json.encode(userUpdate.content['content']));
|
||||
} else if (userUpdate.type == 'presence') {
|
||||
await storePresence(clientId, userUpdate.eventType, userUpdate.content['sender'], json.encode(userUpdate.content['content']));
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores an EventUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeEventUpdate(int clientId, sdk.EventUpdate eventUpdate) async {
|
||||
if (eventUpdate.type == 'ephemeral') return;
|
||||
final eventContent = eventUpdate.content;
|
||||
final type = eventUpdate.type;
|
||||
final chatId = eventUpdate.roomID;
|
||||
|
||||
// Get the state_key for state events
|
||||
var stateKey = '';
|
||||
if (eventContent['state_key'] is String) {
|
||||
stateKey = eventContent['state_key'];
|
||||
}
|
||||
|
||||
if (eventUpdate.eventType == 'm.room.redaction') {
|
||||
await redactMessage(clientId, eventUpdate);
|
||||
}
|
||||
|
||||
if (type == 'timeline' || type == 'history') {
|
||||
// calculate the status
|
||||
var status = 2;
|
||||
if (eventContent['status'] is num) status = eventContent['status'];
|
||||
if ((status == 1 || status == -1) &&
|
||||
eventContent['unsigned'] is Map<String, dynamic> &&
|
||||
eventContent['unsigned']['transaction_id'] is String) {
|
||||
// status changed and we have an old transaction id --> update event id and stuffs
|
||||
await updateEventStatus(status, eventContent['event_id'], clientId, eventContent['unsigned']['transaction_id'], chatId);
|
||||
} else {
|
||||
DbEvent oldEvent;
|
||||
if (type == 'history') {
|
||||
final allOldEvents = await getEvent(clientId, eventContent['event_id'], chatId).get();
|
||||
if (allOldEvents.isNotEmpty) {
|
||||
oldEvent = allOldEvents.first;
|
||||
}
|
||||
}
|
||||
await storeEvent(
|
||||
clientId,
|
||||
eventContent['event_id'],
|
||||
chatId,
|
||||
oldEvent?.sortOrder ?? eventUpdate.sortOrder,
|
||||
eventContent['origin_server_ts'] != null ? DateTime.fromMillisecondsSinceEpoch(eventContent['origin_server_ts']) : DateTime.now(),
|
||||
eventContent['sender'],
|
||||
eventContent['type'],
|
||||
json.encode(eventContent['unsigned'] ?? ''),
|
||||
json.encode(eventContent['content']),
|
||||
json.encode(eventContent['prevContent']),
|
||||
eventContent['state_key'],
|
||||
status,
|
||||
);
|
||||
}
|
||||
|
||||
// is there a transaction id? Then delete the event with this id.
|
||||
if (status != -1 &&
|
||||
eventUpdate.content.containsKey('unsigned') &&
|
||||
eventUpdate.content['unsigned']['transaction_id'] is String) {
|
||||
await removeEvent(clientId, eventUpdate.content['unsigned']['transaction_id'], chatId);
|
||||
}
|
||||
}
|
||||
|
||||
if (type == 'history') return;
|
||||
|
||||
if (type != 'account_data') {
|
||||
final now = DateTime.now();
|
||||
await storeRoomState(
|
||||
clientId,
|
||||
eventContent['event_id'] ?? now.millisecondsSinceEpoch.toString(),
|
||||
chatId,
|
||||
eventUpdate.sortOrder ?? 0.0,
|
||||
eventContent['origin_server_ts'] != null ? DateTime.fromMillisecondsSinceEpoch(eventContent['origin_server_ts']) : now,
|
||||
eventContent['sender'],
|
||||
eventContent['type'],
|
||||
json.encode(eventContent['unsigned'] ?? ''),
|
||||
json.encode(eventContent['content']),
|
||||
json.encode(eventContent['prev_content'] ?? ''),
|
||||
stateKey,
|
||||
);
|
||||
} else if (type == 'account_data') {
|
||||
await storeRoomAccountData(
|
||||
clientId,
|
||||
eventContent['type'],
|
||||
chatId,
|
||||
json.encode(eventContent['content']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<sdk.Event> getEventById(int clientId, String eventId, sdk.Room room) async {
|
||||
final event = await getEvent(clientId, eventId, room.id).get();
|
||||
if (event.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return sdk.Event.fromDb(event.first, room);
|
||||
}
|
||||
|
||||
Future<bool> redactMessage(int clientId, sdk.EventUpdate eventUpdate) async {
|
||||
final events = await getEvent(clientId, eventUpdate.content['redacts'], eventUpdate.roomID).get();
|
||||
var success = false;
|
||||
for (final dbEvent in events) {
|
||||
final event = sdk.Event.fromDb(dbEvent, null);
|
||||
event.setRedactionEvent(sdk.Event.fromJson(eventUpdate.content, null));
|
||||
final changes1 = await updateEvent(
|
||||
json.encode(event.unsigned ?? ''),
|
||||
json.encode(event.content ?? ''),
|
||||
json.encode(event.prevContent ?? ''),
|
||||
clientId,
|
||||
event.eventId,
|
||||
eventUpdate.roomID,
|
||||
);
|
||||
final changes2 = await updateEvent(
|
||||
json.encode(event.unsigned ?? ''),
|
||||
json.encode(event.content ?? ''),
|
||||
json.encode(event.prevContent ?? ''),
|
||||
clientId,
|
||||
event.eventId,
|
||||
eventUpdate.roomID,
|
||||
);
|
||||
if (changes1 == 1 && changes2 == 1) success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
Future<void> forgetRoom(int clientId, String roomId) async {
|
||||
final setKey = '${clientId};${roomId}';
|
||||
_ensuredRooms.remove(setKey);
|
||||
await (delete(rooms)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go();
|
||||
await (delete(events)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go();
|
||||
await (delete(roomStates)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go();
|
||||
await (delete(roomAccountData)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go();
|
||||
}
|
||||
|
||||
Future<void> clearCache(int clientId) async {
|
||||
await (delete(presences)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(roomAccountData)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(accountData)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(roomStates)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(events)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(rooms)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(outboundGroupSessions)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await storePrevBatch(null, clientId);
|
||||
}
|
||||
|
||||
Future<void> clear(int clientId) async {
|
||||
await clearCache(clientId);
|
||||
await (delete(inboundGroupSessions)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(olmSessions)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(userDeviceKeysKey)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(userDeviceKeys)..where((r) => r.clientId.equals(clientId))).go();
|
||||
await (delete(clients)..where((r) => r.clientId.equals(clientId))).go();
|
||||
}
|
||||
|
||||
Future<sdk.User> getUser(int clientId, String userId, sdk.Room room) async {
|
||||
final res = await dbGetUser(clientId, userId, room.id).get();
|
||||
if (res.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return sdk.Event.fromDb(res.first, room).asUser;
|
||||
}
|
||||
|
||||
Future<List<sdk.Event>> getEventList(int clientId, sdk.Room room) async {
|
||||
final res = await dbGetEventList(clientId, room.id).get();
|
||||
final eventList = <sdk.Event>[];
|
||||
for (final r in res) {
|
||||
eventList.add(sdk.Event.fromDb(r, room));
|
||||
}
|
||||
return eventList;
|
||||
}
|
||||
|
||||
Future<Uint8List> getFile(String mxcUri) async {
|
||||
final res = await dbGetFile(mxcUri).get();
|
||||
if (res.isEmpty) return null;
|
||||
return res.first.bytes;
|
||||
}
|
||||
}
|
5364
lib/src/database/database.g.dart
Normal file
5364
lib/src/database/database.g.dart
Normal file
File diff suppressed because it is too large
Load diff
193
lib/src/database/database.moor
Normal file
193
lib/src/database/database.moor
Normal file
|
@ -0,0 +1,193 @@
|
|||
-- Table definitions
|
||||
|
||||
CREATE TABLE clients (
|
||||
client_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
homeserver_url TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
device_id TEXT,
|
||||
device_name TEXT,
|
||||
prev_batch TEXT,
|
||||
olm_account TEXT,
|
||||
UNIQUE(name)
|
||||
) AS DbClient;
|
||||
|
||||
CREATE TABLE user_device_keys (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
user_id TEXT NOT NULL,
|
||||
outdated BOOLEAN DEFAULT true,
|
||||
UNIQUE(client_id, user_id)
|
||||
) as DbUserDeviceKey;
|
||||
CREATE INDEX user_device_keys_index ON user_device_keys(client_id);
|
||||
|
||||
CREATE TABLE user_device_keys_key (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
user_id TEXT NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
verified BOOLEAN DEFAULT false,
|
||||
blocked BOOLEAN DEFAULT false,
|
||||
UNIQUE(client_id, user_id, device_id)
|
||||
) as DbUserDeviceKeysKey;
|
||||
CREATE INDEX user_device_keys_key_index ON user_device_keys_key(client_id);
|
||||
|
||||
CREATE TABLE olm_sessions (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
identity_key TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
pickle TEXT NOT NULL,
|
||||
UNIQUE(client_id, identity_key, session_id)
|
||||
) AS DbOlmSessions;
|
||||
CREATE INDEX olm_sessions_index ON olm_sessions(client_id);
|
||||
|
||||
CREATE TABLE outbound_group_sessions (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
room_id TEXT NOT NULL,
|
||||
pickle TEXT NOT NULL,
|
||||
device_ids TEXT NOT NULL,
|
||||
UNIQUE(client_id, room_id)
|
||||
) AS DbOutboundGroupSession;
|
||||
CREATE INDEX outbound_group_sessions_index ON outbound_group_sessions(client_id);
|
||||
|
||||
CREATE TABLE inbound_group_sessions (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
room_id TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
pickle TEXT NOT NULL,
|
||||
content TEXT,
|
||||
indexes TEXT,
|
||||
UNIQUE(client_id, room_id, session_id)
|
||||
) AS DbInboundGroupSession;
|
||||
CREATE INDEX inbound_group_sessions_index ON inbound_group_sessions(client_id);
|
||||
|
||||
CREATE TABLE rooms (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
room_id TEXT NOT NULL,
|
||||
membership TEXT NOT NULL,
|
||||
highlight_count INTEGER NOT NULL DEFAULT '0',
|
||||
notification_count INTEGER NOT NULL DEFAULT '0',
|
||||
prev_batch TEXT DEFAULT '',
|
||||
joined_member_count INTEGER NOT NULL DEFAULT '0',
|
||||
invited_member_count INTEGER NOT NULL DEFAULT '0',
|
||||
newest_sort_order DOUBLE NOT NULL DEFAULT '0',
|
||||
oldest_sort_order DOUBLE NOT NULL DEFAULT '0',
|
||||
heroes TEXT DEFAULT '',
|
||||
UNIQUE(client_id, room_id)
|
||||
) AS DbRoom;
|
||||
CREATE INDEX rooms_index ON rooms(client_id);
|
||||
|
||||
CREATE TABLE events (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
event_id TEXT NOT NULL,
|
||||
room_id TEXT NOT NULL,
|
||||
sort_order DOUBLE NOT NULL,
|
||||
origin_server_ts DATETIME NOT NULL,
|
||||
sender TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
unsigned TEXT,
|
||||
content TEXT,
|
||||
prev_content TEXT,
|
||||
state_key TEXT,
|
||||
status INTEGER,
|
||||
UNIQUE(client_id, event_id, room_id)
|
||||
) AS DbEvent;
|
||||
CREATE INDEX events_index ON events(client_id, room_id);
|
||||
|
||||
CREATE TABLE room_states (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
event_id TEXT NOT NULL,
|
||||
room_id TEXT NOT NULL,
|
||||
sort_order DOUBLE NOT NULL,
|
||||
origin_server_ts DATETIME NOT NULL,
|
||||
sender TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
unsigned TEXT,
|
||||
content TEXT,
|
||||
prev_content TEXT,
|
||||
state_key TEXT NOT NULL,
|
||||
UNIQUE(client_id, event_id, room_id),
|
||||
UNIQUE(client_id, room_id, state_key, type)
|
||||
) AS DbRoomState;
|
||||
CREATE INDEX room_states_index ON room_states(client_id);
|
||||
|
||||
CREATE TABLE account_data (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
type TEXT NOT NULL,
|
||||
content TEXT,
|
||||
UNIQUE(client_id, type)
|
||||
) AS DbAccountData;
|
||||
CREATE INDEX account_data_index ON account_data(client_id);
|
||||
|
||||
CREATE TABLE room_account_data (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
type TEXT NOT NULL,
|
||||
room_id TEXT NOT NULL,
|
||||
content TEXT,
|
||||
UNIQUE(client_id, type, room_id)
|
||||
) AS DbRoomAccountData;
|
||||
CREATE INDEX room_account_data_index ON room_account_data(client_id);
|
||||
|
||||
CREATE TABLE presences (
|
||||
client_id INTEGER NOT NULL REFERENCES clients(client_id),
|
||||
type TEXT NOT NULL,
|
||||
sender TEXT NOT NULL,
|
||||
content TEXT,
|
||||
UNIQUE(client_id, type, sender)
|
||||
) AS DbPresence;
|
||||
CREATE INDEX presences_index ON presences(client_id);
|
||||
|
||||
CREATE TABLE files (
|
||||
mxc_uri TEXT NOT NULL PRIMARY KEY,
|
||||
bytes BLOB,
|
||||
saved_at DATETIME,
|
||||
UNIQUE(mxc_uri)
|
||||
) AS DbFile;
|
||||
|
||||
-- named queries
|
||||
|
||||
dbGetClient: SELECT * FROM clients WHERE name = :name;
|
||||
updateClient: UPDATE clients SET homeserver_url = :homeserver_url, token = :token, user_id = :user_id, device_id = :device_id, device_name = :device_name, prev_batch = :prev_batch, olm_account = :olm_account WHERE client_id = :client_id;
|
||||
updateClientKeys: UPDATE clients SET olm_account = :olm_account WHERE client_id = :client_id;
|
||||
storePrevBatch: UPDATE clients SET prev_batch = :prev_batch WHERE client_id = :client_id;
|
||||
getAllUserDeviceKeys: SELECT * FROM user_device_keys WHERE client_id = :client_id;
|
||||
getAllUserDeviceKeysKeys: SELECT * FROM user_device_keys_key WHERE client_id = :client_id;
|
||||
getAllOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id;
|
||||
storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle);
|
||||
getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id;
|
||||
dbGetOutboundGroupSession: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
|
||||
storeOutboundGroupSession: INSERT OR REPLACE INTO outbound_group_sessions (client_id, room_id, pickle, device_ids) VALUES (:client_id, :room_id, :pickle, :device_ids);
|
||||
removeOutboundGroupSession: DELETE FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
|
||||
dbGetInboundGroupSessionKeys: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
|
||||
getAllInboundGroupSessions: SELECT * FROM inbound_group_sessions WHERE client_id = :client_id;
|
||||
storeInboundGroupSession: INSERT OR REPLACE INTO inbound_group_sessions (client_id, room_id, session_id, pickle, content, indexes) VALUES (:client_id, :room_id, :session_id, :pickle, :content, :indexes);
|
||||
storeUserDeviceKeysInfo: INSERT OR REPLACE INTO user_device_keys (client_id, user_id, outdated) VALUES (:client_id, :user_id, :outdated);
|
||||
setVerifiedUserDeviceKey: UPDATE user_device_keys_key SET verified = :verified WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id;
|
||||
setBlockedUserDeviceKey: UPDATE user_device_keys_key SET blocked = :blocked WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id;
|
||||
storeUserDeviceKey: INSERT OR REPLACE INTO user_device_keys_key (client_id, user_id, device_id, content, verified, blocked) VALUES (:client_id, :user_id, :device_id, :content, :verified, :blocked);
|
||||
removeUserDeviceKey: DELETE FROM user_device_keys_key WHERE client_id = :client_id AND user_id = :user_id AND device_id = :device_id;
|
||||
insertClient: INSERT INTO clients (name, homeserver_url, token, user_id, device_id, device_name, prev_batch, olm_account) VALUES (:name, :homeserver_url, :token, :user_id, :device_id, :device_name, :prev_batch, :olm_account);
|
||||
ensureRoomExists: INSERT OR IGNORE INTO rooms (client_id, room_id, membership) VALUES (:client_id, :room_id, :membership);
|
||||
setRoomPrevBatch: UPDATE rooms SET prev_batch = :prev_batch WHERE client_id = :client_id AND room_id = :room_id;
|
||||
updateRoomSortOrder: UPDATE rooms SET oldest_sort_order = :oldest_sort_order, newest_sort_order = :newest_sort_order WHERE client_id = :client_id AND room_id = :room_id;
|
||||
getAllAccountData: SELECT * FROM account_data WHERE client_id = :client_id;
|
||||
storeAccountData: INSERT OR REPLACE INTO account_data (client_id, type, content) VALUES (:client_id, :type, :content);
|
||||
getAllPresences: SELECT * FROM presences WHERE client_id = :client_id;
|
||||
storePresence: INSERT OR REPLACE INTO presences (client_id, type, sender, content) VALUES (:client_id, :type, :sender, :content);
|
||||
updateEvent: UPDATE events SET unsigned = :unsigned, content = :content, prev_content = :prev_content WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id;
|
||||
updateEventStatus: UPDATE events SET status = :status, event_id = :new_event_id WHERE client_id = :client_id AND event_id = :old_event_id AND room_id = :room_id;
|
||||
getAllRoomStates: SELECT * FROM room_states WHERE client_id = :client_id;
|
||||
storeEvent: INSERT OR REPLACE INTO events (client_id, event_id, room_id, sort_order, origin_server_ts, sender, type, unsigned, content, prev_content, state_key, status) VALUES (:client_id, :event_id, :room_id, :sort_order, :origin_server_ts, :sender, :type, :unsigned, :content, :prev_content, :state_key, :status);
|
||||
storeRoomState: INSERT OR REPLACE INTO room_states (client_id, event_id, room_id, sort_order, origin_server_ts, sender, type, unsigned, content, prev_content, state_key) VALUES (:client_id, :event_id, :room_id, :sort_order, :origin_server_ts, :sender, :type, :unsigned, :content, :prev_content, :state_key);
|
||||
getAllRoomAccountData: SELECT * FROM room_account_data WHERE client_id = :client_id;
|
||||
storeRoomAccountData: INSERT OR REPLACE INTO room_account_data (client_id, type, room_id, content) VALUES (:client_id, :type, :room_id, :content);
|
||||
dbGetUser: SELECT * FROM room_states WHERE client_id = :client_id AND type = 'm.room.member' AND state_key = :state_key AND room_id = :room_id;
|
||||
dbGetEventList: SELECT * FROM events WHERE client_id = :client_id AND room_id = :room_id GROUP BY event_id ORDER BY sort_order DESC;
|
||||
getStates: SELECT * FROM room_states WHERE client_id = :client_id AND room_id = :room_id;
|
||||
resetNotificationCount: UPDATE rooms SET notification_count = 0, highlight_count = 0 WHERE client_id = :client_id AND room_id = :room_id;
|
||||
getEvent: SELECT * FROM events WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id;
|
||||
removeEvent: DELETE FROM events WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id;
|
||||
removeRoom: DELETE FROM rooms WHERE client_id = :client_id AND room_id = :room_id;
|
||||
removeRoomEvents: DELETE FROM events WHERE client_id = :client_id AND room_id = :room_id;
|
||||
storeFile: INSERT OR REPLACE INTO files (mxc_uri, bytes, saved_at) VALUES (:mxc_uri, :bytes, :time);
|
||||
dbGetFile: SELECT * FROM files WHERE mxc_uri = :mxc_uri;
|
|
@ -29,6 +29,7 @@ import 'package:http/http.dart' as http;
|
|||
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||
import './room.dart';
|
||||
import 'utils/matrix_localizations.dart';
|
||||
import './database/database.dart' show DbRoomState, DbEvent;
|
||||
|
||||
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
|
||||
class Event {
|
||||
|
@ -96,6 +97,8 @@ class Event {
|
|||
|
||||
User get stateKeyUser => room.getUserByMXIDSync(stateKey);
|
||||
|
||||
double sortOrder;
|
||||
|
||||
Event(
|
||||
{this.status = defaultStatus,
|
||||
this.content,
|
||||
|
@ -107,7 +110,8 @@ class Event {
|
|||
this.unsigned,
|
||||
this.prevContent,
|
||||
this.stateKey,
|
||||
this.room});
|
||||
this.room,
|
||||
this.sortOrder = 0.0});
|
||||
|
||||
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
||||
if (payload is String) {
|
||||
|
@ -122,7 +126,7 @@ class Event {
|
|||
}
|
||||
|
||||
/// Get a State event from a table row or from the event stream.
|
||||
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room) {
|
||||
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room, [double sortOrder]) {
|
||||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||
final prevContent = Event.getMapFromPayload(jsonPayload['prev_content']);
|
||||
|
@ -140,6 +144,31 @@ class Event {
|
|||
: DateTime.now(),
|
||||
unsigned: unsigned,
|
||||
room: room,
|
||||
sortOrder: sortOrder ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get an event from either DbRoomState or DbEvent
|
||||
factory Event.fromDb(dynamic dbEntry, Room room) {
|
||||
if (!(dbEntry is DbRoomState || dbEntry is DbEvent)) {
|
||||
throw('Unknown db type');
|
||||
}
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
final unsigned = Event.getMapFromPayload(dbEntry.unsigned);
|
||||
final prevContent = Event.getMapFromPayload(dbEntry.prevContent);
|
||||
return Event(
|
||||
status: (dbEntry is DbEvent ? dbEntry.status : null) ?? defaultStatus,
|
||||
stateKey: dbEntry.stateKey,
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: dbEntry.type,
|
||||
eventId: dbEntry.eventId,
|
||||
roomId: dbEntry.roomId,
|
||||
senderId: dbEntry.sender,
|
||||
time: dbEntry.originServerTs ?? DateTime.now(),
|
||||
unsigned: unsigned,
|
||||
room: room,
|
||||
sortOrder: dbEntry.sortOrder ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -337,9 +366,7 @@ class Event {
|
|||
/// from the database and the timelines. Returns false if not removed.
|
||||
Future<bool> remove() async {
|
||||
if (status < 1) {
|
||||
if (room.client.store != null) {
|
||||
await room.client.store.removeEvent(eventId);
|
||||
}
|
||||
await room.client.database?.removeEvent(room.client.id, eventId, room.id);
|
||||
|
||||
room.client.onEvent.add(EventUpdate(
|
||||
roomID: room.id,
|
||||
|
@ -349,7 +376,8 @@ class Event {
|
|||
'event_id': eventId,
|
||||
'status': -2,
|
||||
'content': {'body': 'Removed...'}
|
||||
}));
|
||||
},
|
||||
sortOrder: sortOrder));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -470,13 +498,13 @@ class Event {
|
|||
// Is this file storeable?
|
||||
final infoMap =
|
||||
getThumbnail ? content['info']['thumbnail_info'] : content['info'];
|
||||
final storeable = (room.client.storeAPI?.extended ?? false) &&
|
||||
final storeable = room.client.database != null &&
|
||||
infoMap is Map<String, dynamic> &&
|
||||
infoMap['size'] is int &&
|
||||
infoMap['size'] <= room.client.store.maxFileSize;
|
||||
infoMap['size'] <= room.client.database.maxFileSize ;
|
||||
|
||||
if (storeable) {
|
||||
uint8list = await room.client.store.getFile(mxContent.toString());
|
||||
uint8list = await room.client.database.getFile(mxContent.toString());
|
||||
}
|
||||
|
||||
// Download the file
|
||||
|
@ -484,7 +512,7 @@ class Event {
|
|||
uint8list =
|
||||
(await http.get(mxContent.getDownloadLink(room.client))).bodyBytes;
|
||||
if (storeable) {
|
||||
await room.client.store.storeFile(uint8list, mxContent.toString());
|
||||
await room.client.database.storeFile(mxContent.toString(), uint8list, DateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import './database/database.dart' show DbPresence;
|
||||
|
||||
enum PresenceType { online, offline, unavailable }
|
||||
|
||||
/// Informs the client of a user's presence state change.
|
||||
|
@ -39,6 +42,8 @@ class Presence {
|
|||
final String statusMsg;
|
||||
final DateTime time;
|
||||
|
||||
Presence({this.sender, this.displayname, this.avatarUrl, this.currentlyActive, this.lastActiveAgo, this.presence, this.statusMsg, this.time});
|
||||
|
||||
Presence.fromJson(Map<String, dynamic> json)
|
||||
: sender = json['sender'],
|
||||
displayname = json['content']['displayname'],
|
||||
|
@ -55,4 +60,23 @@ class Presence {
|
|||
e.toString() == "PresenceType.${json['content']['presence']}",
|
||||
orElse: () => null),
|
||||
statusMsg = json['content']['status_msg'];
|
||||
|
||||
factory Presence.fromDb(DbPresence dbEntry) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return Presence(
|
||||
sender: dbEntry.sender,
|
||||
displayname: content['displayname'],
|
||||
avatarUrl: content['avatar_url'] != null ? Uri.parse(content['avatar_url']) : null,
|
||||
currentlyActive: content['currently_active'],
|
||||
lastActiveAgo: content['last_active_ago'],
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
DateTime.now().millisecondsSinceEpoch -
|
||||
(content['last_active_ago'] ?? 0)),
|
||||
presence: PresenceType.values.firstWhere(
|
||||
(e) =>
|
||||
e.toString() == "PresenceType.${content['presence']}",
|
||||
orElse: () => null),
|
||||
statusMsg: content['status_msg'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import 'timeline.dart';
|
|||
import 'utils/matrix_localizations.dart';
|
||||
import 'utils/states_map.dart';
|
||||
import './utils/markdown.dart';
|
||||
import './database/database.dart' show DbRoom;
|
||||
|
||||
enum PushRuleState { notify, mentions_only, dont_notify }
|
||||
enum JoinRules { public, knock, invite, private }
|
||||
|
@ -90,6 +91,27 @@ class Room {
|
|||
|
||||
List<String> _outboundGroupSessionDevices;
|
||||
|
||||
double _newestSortOrder;
|
||||
double _oldestSortOrder;
|
||||
|
||||
double get newSortOrder {
|
||||
_newestSortOrder++;
|
||||
return _newestSortOrder;
|
||||
}
|
||||
|
||||
double get oldSortOrder {
|
||||
_oldestSortOrder--;
|
||||
return _oldestSortOrder;
|
||||
}
|
||||
|
||||
void resetSortOrder() {
|
||||
_oldestSortOrder = _newestSortOrder = 0.0;
|
||||
}
|
||||
|
||||
Future<void> updateSortOrder() async {
|
||||
await client.database?.updateRoomSortOrder(_oldestSortOrder, _newestSortOrder, client.id, id);
|
||||
}
|
||||
|
||||
/// Clears the existing outboundGroupSession, tries to create a new one and
|
||||
/// stores it as an ingoingGroupSession in the [sessionKeys]. Then sends the
|
||||
/// new session encrypted with olm to all non-blocked devices using
|
||||
|
@ -120,7 +142,7 @@ class Room {
|
|||
'session_id': outboundGroupSession.session_id(),
|
||||
'session_key': outboundGroupSession.session_key(),
|
||||
};
|
||||
setSessionKey(rawSession['session_id'], rawSession);
|
||||
setInboundGroupSession(rawSession['session_id'], rawSession);
|
||||
try {
|
||||
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
|
||||
_outboundGroupSession = outboundGroupSession;
|
||||
|
@ -137,12 +159,10 @@ class Room {
|
|||
|
||||
Future<void> _storeOutboundGroupSession() async {
|
||||
if (_outboundGroupSession == null) return;
|
||||
await client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session',
|
||||
_outboundGroupSession.pickle(client.userID));
|
||||
await client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices',
|
||||
json.encode(_outboundGroupSessionDevices));
|
||||
await client.database?.storeOutboundGroupSession(
|
||||
client.id, id, _outboundGroupSession.pickle(client.userID),
|
||||
json.encode(_outboundGroupSessionDevices),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -162,12 +182,11 @@ class Room {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
_outboundGroupSessionDevices == null;
|
||||
await client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session', null);
|
||||
await client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices',
|
||||
null);
|
||||
if (!wipe && _outboundGroupSessionDevices == null && _outboundGroupSession == null) {
|
||||
return true; // let's just short-circuit out of here, no need to do DB stuff
|
||||
}
|
||||
_outboundGroupSessionDevices = null;
|
||||
await client.database?.removeOutboundGroupSession(client.id, id);
|
||||
_outboundGroupSession?.free();
|
||||
_outboundGroupSession = null;
|
||||
return true;
|
||||
|
@ -181,13 +200,13 @@ class Room {
|
|||
/// "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
|
||||
/// "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
|
||||
/// }
|
||||
Map<String, SessionKey> get sessionKeys => _sessionKeys;
|
||||
Map<String, SessionKey> _sessionKeys = {};
|
||||
Map<String, SessionKey> get inboundGroupSessions => _inboundGroupSessions;
|
||||
Map<String, SessionKey> _inboundGroupSessions = {};
|
||||
|
||||
/// Add a new session key to the [sessionKeys].
|
||||
void setSessionKey(String sessionId, Map<String, dynamic> content,
|
||||
void setInboundGroupSession(String sessionId, Map<String, dynamic> content,
|
||||
{bool forwarded = false}) {
|
||||
if (sessionKeys.containsKey(sessionId)) return;
|
||||
if (inboundGroupSessions.containsKey(sessionId)) return;
|
||||
olm.InboundGroupSession inboundGroupSession;
|
||||
if (content['algorithm'] == 'm.megolm.v1.aes-sha2') {
|
||||
try {
|
||||
|
@ -203,16 +222,17 @@ class Room {
|
|||
e.toString());
|
||||
}
|
||||
}
|
||||
_sessionKeys[sessionId] = SessionKey(
|
||||
_inboundGroupSessions[sessionId] = SessionKey(
|
||||
content: content,
|
||||
inboundGroupSession: inboundGroupSession,
|
||||
indexes: {},
|
||||
key: client.userID,
|
||||
);
|
||||
if (_fullyRestored) {
|
||||
client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/session_keys',
|
||||
json.encode(sessionKeys));
|
||||
client.database?.storeInboundGroupSession(client.id, id, sessionId,
|
||||
inboundGroupSession.pickle(client.userID), json.encode(content),
|
||||
json.encode({}),
|
||||
);
|
||||
}
|
||||
_tryAgainDecryptLastMessage();
|
||||
onSessionKeyReceived.add(sessionId);
|
||||
|
@ -395,7 +415,9 @@ class Room {
|
|||
this.mInvitedMemberCount = 0,
|
||||
this.mJoinedMemberCount = 0,
|
||||
this.roomAccountData = const {},
|
||||
});
|
||||
double newestSortOrder = 0.0,
|
||||
double oldestSortOrder = 0.0,
|
||||
}) : _newestSortOrder = newestSortOrder, _oldestSortOrder = oldestSortOrder;
|
||||
|
||||
/// The default count of how much events should be requested when requesting the
|
||||
/// history of this room.
|
||||
|
@ -746,20 +768,22 @@ class Room {
|
|||
};
|
||||
}
|
||||
|
||||
final sortOrder = newSortOrder;
|
||||
// Display a *sending* event and store it.
|
||||
var eventUpdate =
|
||||
EventUpdate(type: 'timeline', roomID: id, eventType: type, content: {
|
||||
'type': type,
|
||||
'event_id': messageID,
|
||||
'sender': client.userID,
|
||||
'status': 0,
|
||||
'origin_server_ts': now,
|
||||
'content': content
|
||||
});
|
||||
var eventUpdate = EventUpdate(type: 'timeline', roomID: id, eventType: type, sortOrder: sortOrder,
|
||||
content: {
|
||||
'type': type,
|
||||
'event_id': messageID,
|
||||
'sender': client.userID,
|
||||
'status': 0,
|
||||
'origin_server_ts': now,
|
||||
'content': content
|
||||
},
|
||||
);
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
await client.database?.transaction(() async {
|
||||
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||
await updateSortOrder();
|
||||
});
|
||||
|
||||
// Send the text and on success, store and display a *sent* event.
|
||||
|
@ -767,7 +791,7 @@ class Room {
|
|||
final response = await client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: '/client/r0/rooms/${id}/send/$sendType/$messageID',
|
||||
data: client.encryptionEnabled
|
||||
data: encrypted && client.encryptionEnabled
|
||||
? await encryptGroupMessagePayload(content)
|
||||
: content);
|
||||
final String res = response['event_id'];
|
||||
|
@ -775,9 +799,8 @@ class Room {
|
|||
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
||||
eventUpdate.content['event_id'] = res;
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
await client.database?.transaction(() async {
|
||||
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||
});
|
||||
return res;
|
||||
} catch (exception) {
|
||||
|
@ -786,9 +809,8 @@ class Room {
|
|||
eventUpdate.content['status'] = -1;
|
||||
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
||||
client.onEvent.add(eventUpdate);
|
||||
await client.store?.transaction(() {
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
return;
|
||||
await client.database?.transaction(() async {
|
||||
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
|
@ -809,7 +831,7 @@ class Room {
|
|||
}
|
||||
} on MatrixException catch (exception) {
|
||||
if (exception.errorMessage == 'No known servers') {
|
||||
await client.store?.forgetRoom(id);
|
||||
await client.database?.forgetRoom(client.id, id);
|
||||
client.onRoomUpdate.add(
|
||||
RoomUpdate(
|
||||
id: id,
|
||||
|
@ -833,7 +855,7 @@ class Room {
|
|||
|
||||
/// Call the Matrix API to forget this room if you already left it.
|
||||
Future<void> forget() async {
|
||||
await client.store?.forgetRoom(id);
|
||||
await client.database?.forgetRoom(client.id, id);
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST, action: '/client/r0/rooms/${id}/forget');
|
||||
return;
|
||||
|
@ -903,65 +925,55 @@ class Room {
|
|||
|
||||
if (onHistoryReceived != null) onHistoryReceived();
|
||||
prev_batch = resp['end'];
|
||||
await client.store?.storeRoomPrevBatch(this);
|
||||
|
||||
final dbActions = <Future<dynamic> Function()>[];
|
||||
if (client.database != null) {
|
||||
dbActions.add(() => client.database.setRoomPrevBatch(prev_batch, client.id, id));
|
||||
}
|
||||
|
||||
if (!(resp['chunk'] is List<dynamic> &&
|
||||
resp['chunk'].length > 0 &&
|
||||
resp['end'] is String)) return;
|
||||
|
||||
if (resp['state'] is List<dynamic>) {
|
||||
await client.store?.transaction(() {
|
||||
for (var i = 0; i < resp['state'].length; i++) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'state',
|
||||
roomID: id,
|
||||
eventType: resp['state'][i]['type'],
|
||||
content: resp['state'][i],
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
}
|
||||
return;
|
||||
});
|
||||
if (client.store == null) {
|
||||
for (var i = 0; i < resp['state'].length; i++) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'state',
|
||||
roomID: id,
|
||||
eventType: resp['state'][i]['type'],
|
||||
content: resp['state'][i],
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
for (final state in resp['state']) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'state',
|
||||
roomID: id,
|
||||
eventType: state['type'],
|
||||
content: state,
|
||||
sortOrder: oldSortOrder,
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
if (client.database != null) {
|
||||
dbActions.add(() => client.database.storeEventUpdate(client.id, eventUpdate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> history = resp['chunk'];
|
||||
await client.store?.transaction(() {
|
||||
for (var i = 0; i < history.length; i++) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'history',
|
||||
roomID: id,
|
||||
eventType: history[i]['type'],
|
||||
content: history[i],
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
client.store.storeEventUpdate(eventUpdate);
|
||||
client.store.setRoomPrevBatch(id, resp['end']);
|
||||
}
|
||||
return;
|
||||
});
|
||||
if (client.store == null) {
|
||||
for (var i = 0; i < history.length; i++) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'history',
|
||||
roomID: id,
|
||||
eventType: history[i]['type'],
|
||||
content: history[i],
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
for (final hist in history) {
|
||||
var eventUpdate = EventUpdate(
|
||||
type: 'history',
|
||||
roomID: id,
|
||||
eventType: hist['type'],
|
||||
content: hist,
|
||||
sortOrder: oldSortOrder,
|
||||
).decrypt(this);
|
||||
client.onEvent.add(eventUpdate);
|
||||
if (client.database != null) {
|
||||
dbActions.add(() => client.database.storeEventUpdate(client.id, eventUpdate));
|
||||
}
|
||||
}
|
||||
if (client.database != null) {
|
||||
dbActions.add(() => client.database.setRoomPrevBatch(resp['end'], client.id, id));
|
||||
}
|
||||
await client.database?.transaction(() async {
|
||||
for (final f in dbActions) {
|
||||
await f();
|
||||
}
|
||||
await updateSortOrder();
|
||||
});
|
||||
client.onRoomUpdate.add(
|
||||
RoomUpdate(
|
||||
id: id,
|
||||
|
@ -1013,7 +1025,7 @@ class Room {
|
|||
/// Sends *m.fully_read* and *m.read* for the given event ID.
|
||||
Future<void> sendReadReceipt(String eventID) async {
|
||||
notificationCount = 0;
|
||||
await client?.store?.resetNotificationCount(id);
|
||||
await client.database?.resetNotificationCount(client.id, id);
|
||||
await client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: '/client/r0/rooms/$id/read_markers',
|
||||
|
@ -1024,48 +1036,44 @@ class Room {
|
|||
return;
|
||||
}
|
||||
|
||||
Future<void> restoreGroupSessionKeys() async {
|
||||
Future<void> restoreGroupSessionKeys({
|
||||
dynamic outboundGroupSession, // DbOutboundGroupSession, optionally as future
|
||||
dynamic inboundGroupSessions, // DbSessionKey, as iterator and optionally as future
|
||||
}) async {
|
||||
// Restore the inbound and outbound session keys
|
||||
if (client.encryptionEnabled && client.storeAPI != null) {
|
||||
final String outboundGroupSessionPickle = await client.storeAPI.getItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session');
|
||||
if (outboundGroupSessionPickle != null) {
|
||||
if (client.encryptionEnabled && client.database != null) {
|
||||
outboundGroupSession ??= client.database.getDbOutboundGroupSession(client.id, id);
|
||||
inboundGroupSessions ??= client.database.getDbInboundGroupSessions(client.id, id);
|
||||
if (outboundGroupSession is Future) {
|
||||
outboundGroupSession = await outboundGroupSession;
|
||||
}
|
||||
if (inboundGroupSessions is Future) {
|
||||
inboundGroupSessions = await inboundGroupSessions;
|
||||
}
|
||||
if (outboundGroupSession != false && outboundGroupSession != null) {
|
||||
try {
|
||||
_outboundGroupSession = olm.OutboundGroupSession();
|
||||
_outboundGroupSession.unpickle(
|
||||
client.userID, outboundGroupSessionPickle);
|
||||
client.userID, outboundGroupSession.pickle);
|
||||
} catch (e) {
|
||||
_outboundGroupSession = null;
|
||||
print('[LibOlm] Unable to unpickle outboundGroupSession: ' +
|
||||
e.toString());
|
||||
}
|
||||
}
|
||||
final String outboundGroupSessionDevicesString = await client.storeAPI
|
||||
.getItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices');
|
||||
if (outboundGroupSessionDevicesString != null) {
|
||||
_outboundGroupSessionDevices =
|
||||
List<String>.from(json.decode(outboundGroupSessionDevicesString));
|
||||
List<String>.from(json.decode(outboundGroupSession.deviceIds));
|
||||
}
|
||||
final String sessionKeysPickle = await client.storeAPI
|
||||
.getItem('/clients/${client.deviceID}/rooms/${id}/session_keys');
|
||||
if (sessionKeysPickle?.isNotEmpty ?? false) {
|
||||
final Map<String, dynamic> map = json.decode(sessionKeysPickle);
|
||||
_sessionKeys ??= {};
|
||||
for (var entry in map.entries) {
|
||||
if (inboundGroupSessions?.isNotEmpty ?? false) {
|
||||
_inboundGroupSessions ??= {};
|
||||
for (final sessionKey in inboundGroupSessions) {
|
||||
try {
|
||||
_sessionKeys[entry.key] =
|
||||
SessionKey.fromJson(entry.value, client.userID);
|
||||
_inboundGroupSessions[sessionKey.sessionId] = SessionKey.fromDb(sessionKey, client.userID);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle inboundGroupSession: ' +
|
||||
e.toString());
|
||||
print('[LibOlm] Could not unpickle inboundGroupSession: ' + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.storeAPI?.setItem(
|
||||
'/clients/${client.deviceID}/rooms/${id}/session_keys',
|
||||
json.encode(sessionKeys));
|
||||
_tryAgainDecryptLastMessage();
|
||||
_fullyRestored = true;
|
||||
return;
|
||||
|
@ -1076,44 +1084,64 @@ class Room {
|
|||
/// Returns a Room from a json String which comes normally from the store. If the
|
||||
/// state are also given, the method will await them.
|
||||
static Future<Room> getRoomFromTableRow(
|
||||
Map<String, dynamic> row, Client matrix,
|
||||
{Future<List<Map<String, dynamic>>> states,
|
||||
Future<List<Map<String, dynamic>>> roomAccountData}) async {
|
||||
var newRoom = Room(
|
||||
id: row['room_id'],
|
||||
DbRoom row, // either Map<String, dynamic> or DbRoom
|
||||
Client matrix,
|
||||
{
|
||||
dynamic states, // DbRoomState, as iterator and optionally as future
|
||||
dynamic roomAccountData, // DbRoomAccountData, as iterator and optionally as future
|
||||
dynamic outboundGroupSession, // DbOutboundGroupSession, optionally as future
|
||||
dynamic inboundGroupSessions, // DbSessionKey, as iterator and optionally as future
|
||||
}) async {
|
||||
final newRoom = Room(
|
||||
id: row.roomId,
|
||||
membership: Membership.values
|
||||
.firstWhere((e) => e.toString() == 'Membership.' + row['membership']),
|
||||
notificationCount: row['notification_count'],
|
||||
highlightCount: row['highlight_count'],
|
||||
notificationSettings: row['notification_settings'],
|
||||
prev_batch: row['prev_batch'],
|
||||
mInvitedMemberCount: row['invited_member_count'],
|
||||
mJoinedMemberCount: row['joined_member_count'],
|
||||
mHeroes: row['heroes']?.split(',') ?? [],
|
||||
.firstWhere((e) => e.toString() == 'Membership.' + row.membership),
|
||||
notificationCount: row.notificationCount,
|
||||
highlightCount: row.highlightCount,
|
||||
notificationSettings: 'mention', // TODO: do proper things
|
||||
prev_batch: row.prevBatch,
|
||||
mInvitedMemberCount: row.invitedMemberCount,
|
||||
mJoinedMemberCount: row.joinedMemberCount,
|
||||
mHeroes: row.heroes?.split(',') ?? [],
|
||||
client: matrix,
|
||||
roomAccountData: {},
|
||||
newestSortOrder: row.newestSortOrder,
|
||||
oldestSortOrder: row.oldestSortOrder,
|
||||
);
|
||||
|
||||
// Restore the inbound and outbound session keys
|
||||
await newRoom.restoreGroupSessionKeys();
|
||||
|
||||
if (states != null) {
|
||||
var rawStates = await states;
|
||||
for (var i = 0; i < rawStates.length; i++) {
|
||||
var newState = Event.fromJson(rawStates[i], newRoom);
|
||||
var rawStates;
|
||||
if (states is Future) {
|
||||
rawStates = await states;
|
||||
} else {
|
||||
rawStates = states;
|
||||
}
|
||||
for (final rawState in rawStates) {
|
||||
final newState = Event.fromDb(rawState, newRoom);;
|
||||
newRoom.setState(newState);
|
||||
}
|
||||
}
|
||||
|
||||
var newRoomAccountData = <String, RoomAccountData>{};
|
||||
if (roomAccountData != null) {
|
||||
var rawRoomAccountData = await roomAccountData;
|
||||
for (var i = 0; i < rawRoomAccountData.length; i++) {
|
||||
var newData = RoomAccountData.fromJson(rawRoomAccountData[i], newRoom);
|
||||
var rawRoomAccountData;
|
||||
if (roomAccountData is Future) {
|
||||
rawRoomAccountData = await roomAccountData;
|
||||
} else {
|
||||
rawRoomAccountData = roomAccountData;
|
||||
}
|
||||
for (final singleAccountData in rawRoomAccountData) {
|
||||
final newData = RoomAccountData.fromDb(singleAccountData, newRoom);
|
||||
newRoomAccountData[newData.typeKey] = newData;
|
||||
}
|
||||
newRoom.roomAccountData = newRoomAccountData;
|
||||
}
|
||||
newRoom.roomAccountData = newRoomAccountData;
|
||||
|
||||
// Restore the inbound and outbound session keys
|
||||
await newRoom.restoreGroupSessionKeys(
|
||||
outboundGroupSession: outboundGroupSession,
|
||||
inboundGroupSessions: inboundGroupSessions,
|
||||
);
|
||||
|
||||
return newRoom;
|
||||
}
|
||||
|
@ -1122,24 +1150,28 @@ class Room {
|
|||
Future<Timeline> getTimeline(
|
||||
{onTimelineUpdateCallback onUpdate,
|
||||
onTimelineInsertCallback onInsert}) async {
|
||||
var events = client.store != null
|
||||
? await client.store.getEventList(this)
|
||||
: <Event>[];
|
||||
var events;
|
||||
if (client.database != null) {
|
||||
events = await client.database.getEventList(client.id, this);
|
||||
} else {
|
||||
events = <Event>[];
|
||||
}
|
||||
|
||||
// Try again to decrypt encrypted events and update the database.
|
||||
if (encrypted && client.store != null) {
|
||||
await client.store.transaction(() {
|
||||
if (encrypted && client.database != null) {
|
||||
await client.database.transaction(() async {
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].type == EventTypes.Encrypted &&
|
||||
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) {
|
||||
events[i] = events[i].decrypted;
|
||||
if (events[i].type != EventTypes.Encrypted) {
|
||||
client.store.storeEventUpdate(
|
||||
await client.database.storeEventUpdate(client.id,
|
||||
EventUpdate(
|
||||
eventType: events[i].typeKey,
|
||||
content: events[i].toJson(),
|
||||
roomID: events[i].roomId,
|
||||
type: 'timeline',
|
||||
sortOrder: events[i].sortOrder,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1154,7 +1186,7 @@ class Room {
|
|||
onUpdate: onUpdate,
|
||||
onInsert: onInsert,
|
||||
);
|
||||
if (client.store == null) {
|
||||
if (client.database == null) {
|
||||
prev_batch = '';
|
||||
await requestHistory(historyCount: 10);
|
||||
}
|
||||
|
@ -1227,6 +1259,9 @@ class Room {
|
|||
/// Requests a missing [User] for this room. Important for clients using
|
||||
/// lazy loading.
|
||||
Future<User> requestUser(String mxID, {bool ignoreErrors = false}) async {
|
||||
if (getState('m.room.member', mxID) != null) {
|
||||
return getState('m.room.member', mxID).asUser;
|
||||
}
|
||||
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
||||
Map<String, dynamic> resp;
|
||||
try {
|
||||
|
@ -1237,23 +1272,30 @@ class Room {
|
|||
_requestingMatrixIds.remove(mxID);
|
||||
if (!ignoreErrors) rethrow;
|
||||
}
|
||||
if (resp == null) {
|
||||
return null;
|
||||
}
|
||||
final user = User(mxID,
|
||||
displayName: resp['displayname'],
|
||||
avatarUrl: resp['avatar_url'],
|
||||
room: this);
|
||||
states[mxID] = user;
|
||||
if (client.store != null) {
|
||||
await client.store.transaction(() {
|
||||
client.store.storeEventUpdate(
|
||||
EventUpdate(
|
||||
content: resp,
|
||||
roomID: id,
|
||||
type: 'state',
|
||||
eventType: 'm.room.member'),
|
||||
);
|
||||
return;
|
||||
});
|
||||
}
|
||||
await client.database?.transaction(() async {
|
||||
final content = <String, dynamic>{
|
||||
'sender': mxID,
|
||||
'type': 'm.room.member',
|
||||
'content': resp,
|
||||
'state_key': mxID,
|
||||
};
|
||||
await client.database.storeEventUpdate(client.id,
|
||||
EventUpdate(
|
||||
content: content,
|
||||
roomID: id,
|
||||
type: 'state',
|
||||
eventType: 'm.room.member',
|
||||
sortOrder: 0.0),
|
||||
);
|
||||
});
|
||||
if (onUpdate != null) onUpdate.add(id);
|
||||
_requestingMatrixIds.remove(mxID);
|
||||
return user;
|
||||
|
@ -1690,12 +1732,11 @@ class Room {
|
|||
Future<List<DeviceKeys>> getUserDeviceKeys() async {
|
||||
var deviceKeys = <DeviceKeys>[];
|
||||
var users = await requestParticipants();
|
||||
for (final userDeviceKeyEntry in client.userDeviceKeys.entries) {
|
||||
if (users.indexWhere((u) => u.id == userDeviceKeyEntry.key) == -1) {
|
||||
continue;
|
||||
}
|
||||
for (var deviceKeyEntry in userDeviceKeyEntry.value.deviceKeys.values) {
|
||||
deviceKeys.add(deviceKeyEntry);
|
||||
for (final user in users) {
|
||||
if (client.userDeviceKeys.containsKey(user.id)) {
|
||||
for (var deviceKeyEntry in client.userDeviceKeys[user.id].deviceKeys.values) {
|
||||
deviceKeys.add(deviceKeyEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceKeys;
|
||||
|
@ -1745,23 +1786,23 @@ class Room {
|
|||
throw (DecryptError.UNKNOWN_ALGORITHM);
|
||||
}
|
||||
final String sessionId = event.content['session_id'];
|
||||
if (!sessionKeys.containsKey(sessionId)) {
|
||||
if (!inboundGroupSessions.containsKey(sessionId)) {
|
||||
throw (DecryptError.UNKNOWN_SESSION);
|
||||
}
|
||||
final decryptResult = sessionKeys[sessionId]
|
||||
final decryptResult = inboundGroupSessions[sessionId]
|
||||
.inboundGroupSession
|
||||
.decrypt(event.content['ciphertext']);
|
||||
final messageIndexKey =
|
||||
event.eventId + event.time.millisecondsSinceEpoch.toString();
|
||||
if (sessionKeys[sessionId].indexes.containsKey(messageIndexKey) &&
|
||||
sessionKeys[sessionId].indexes[messageIndexKey] !=
|
||||
if (inboundGroupSessions[sessionId].indexes.containsKey(messageIndexKey) &&
|
||||
inboundGroupSessions[sessionId].indexes[messageIndexKey] !=
|
||||
decryptResult.message_index) {
|
||||
if ((_outboundGroupSession?.session_id() ?? '') == sessionId) {
|
||||
clearOutboundGroupSession();
|
||||
}
|
||||
throw (DecryptError.CHANNEL_CORRUPTED);
|
||||
}
|
||||
sessionKeys[sessionId].indexes[messageIndexKey] =
|
||||
inboundGroupSessions[sessionId].indexes[messageIndexKey] =
|
||||
decryptResult.message_index;
|
||||
_storeOutboundGroupSession();
|
||||
decryptedPayload = json.decode(decryptResult.plaintext);
|
||||
|
@ -1799,6 +1840,7 @@ class Room {
|
|||
stateKey: event.stateKey,
|
||||
prevContent: event.prevContent,
|
||||
status: event.status,
|
||||
sortOrder: event.sortOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/account_data.dart';
|
||||
import 'package:famedlysdk/src/event.dart';
|
||||
import './database/database.dart' show DbRoomAccountData;
|
||||
|
||||
/// Stripped down events for account data and ephemrals of a room.
|
||||
class RoomAccountData extends AccountData {
|
||||
|
@ -46,4 +47,14 @@ class RoomAccountData extends AccountData {
|
|||
roomId: jsonPayload['room_id'],
|
||||
room: room);
|
||||
}
|
||||
|
||||
/// get room account data from DbRoomAccountData
|
||||
factory RoomAccountData.fromDb(DbRoomAccountData dbEntry, Room room) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return RoomAccountData(
|
||||
content: content,
|
||||
typeKey: dbEntry.type,
|
||||
roomId: dbEntry.roomId,
|
||||
room: room);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* 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:async';
|
||||
import 'dart:core';
|
||||
import 'dart:typed_data';
|
||||
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';
|
||||
import 'user.dart';
|
||||
import 'sync/event_update.dart';
|
||||
import 'sync/room_update.dart';
|
||||
import 'sync/user_update.dart';
|
||||
|
||||
abstract class StoreAPI {
|
||||
/// Whether this is a simple store which only stores the client credentials and
|
||||
/// end to end encryption stuff or the whole sync payloads.
|
||||
final bool extended = false;
|
||||
|
||||
/// Link back to the client.
|
||||
Client client;
|
||||
|
||||
/// Will be automatically called when the client is logged in successfully.
|
||||
Future<void> storeClient();
|
||||
|
||||
/// Clears all tables from the database.
|
||||
Future<void> clear();
|
||||
|
||||
Future<dynamic> getItem(String key);
|
||||
|
||||
Future<void> setItem(String key, String value);
|
||||
|
||||
Future<Map<String, DeviceKeysList>> getUserDeviceKeys();
|
||||
|
||||
Future<void> storeUserDeviceKeys(Map<String, DeviceKeysList> userDeviceKeys);
|
||||
}
|
||||
|
||||
/// Responsible to store all data persistent and to query objects from the
|
||||
/// database.
|
||||
abstract class ExtendedStoreAPI extends StoreAPI {
|
||||
/// The maximum size of files which should be stored in bytes.
|
||||
int get maxFileSize => 1 * 1024 * 1024;
|
||||
|
||||
/// Whether this is a simple store which only stores the client credentials and
|
||||
/// end to end encryption stuff or the whole sync payloads.
|
||||
@override
|
||||
final bool extended = true;
|
||||
|
||||
/// The current trans
|
||||
Future<void> setRoomPrevBatch(String roomId, String prevBatch);
|
||||
|
||||
/// Performs these query or queries inside of an transaction.
|
||||
Future<void> transaction(void Function() queries);
|
||||
|
||||
/// Will be automatically called on every synchronisation. Must be called inside of
|
||||
// /// [transaction].
|
||||
void storePrevBatch(String prevBatch);
|
||||
|
||||
Future<void> storeRoomPrevBatch(Room room);
|
||||
|
||||
/// Stores a RoomUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeRoomUpdate(RoomUpdate roomUpdate);
|
||||
|
||||
/// Stores an UserUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeUserEventUpdate(UserUpdate userUpdate);
|
||||
|
||||
/// Stores an EventUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeEventUpdate(EventUpdate eventUpdate);
|
||||
|
||||
/// Returns a User object by a given Matrix ID and a Room.
|
||||
Future<User> getUser({String matrixID, Room room});
|
||||
|
||||
/// Returns a list of events for the given room and sets all participants.
|
||||
Future<List<Event>> getEventList(Room room);
|
||||
|
||||
/// Returns all rooms, the client is participating. Excludes left rooms.
|
||||
Future<List<Room>> getRoomList({bool onlyLeft = false});
|
||||
|
||||
/// Deletes this room from the database.
|
||||
Future<void> forgetRoom(String roomID);
|
||||
|
||||
/// Sets notification and highlight count to 0 for this room.
|
||||
Future<void> resetNotificationCount(String roomID);
|
||||
|
||||
/// Searches for the event in the store.
|
||||
Future<Event> getEventById(String eventID, Room room);
|
||||
|
||||
/// Returns all account data for this client.
|
||||
Future<Map<String, AccountData>> getAccountData();
|
||||
|
||||
/// Returns all stored presences for this client.
|
||||
Future<Map<String, Presence>> getPresences();
|
||||
|
||||
/// Removes this event from the store.
|
||||
Future removeEvent(String eventId);
|
||||
|
||||
/// Stores the bytes of this file indexed by the [mxcUri]. Throws an
|
||||
/// exception if the bytes are more than [MAX_FILE_SIZE].
|
||||
Future<void> storeFile(Uint8List bytes, String mxcUri);
|
||||
|
||||
/// Returns the file bytes indexed by [mxcUri]. Returns null if not found.
|
||||
Future<Uint8List> getFile(String mxcUri);
|
||||
}
|
|
@ -40,7 +40,10 @@ class EventUpdate {
|
|||
// The json payload of the content of this event.
|
||||
final Map<String, dynamic> content;
|
||||
|
||||
EventUpdate({this.eventType, this.roomID, this.type, this.content});
|
||||
// the order where to stort this event
|
||||
final double sortOrder;
|
||||
|
||||
EventUpdate({this.eventType, this.roomID, this.type, this.content, this.sortOrder});
|
||||
|
||||
EventUpdate decrypt(Room room) {
|
||||
if (eventType != 'm.room.encrypted') {
|
||||
|
@ -48,12 +51,13 @@ class EventUpdate {
|
|||
}
|
||||
try {
|
||||
var decrpytedEvent =
|
||||
room.decryptGroupMessage(Event.fromJson(content, room));
|
||||
room.decryptGroupMessage(Event.fromJson(content, room, sortOrder));
|
||||
return EventUpdate(
|
||||
eventType: eventType,
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: decrpytedEvent.toJson(),
|
||||
sortOrder: sortOrder,
|
||||
);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
|
||||
|
|
|
@ -122,7 +122,7 @@ class Timeline {
|
|||
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
||||
if (eventId != null) {
|
||||
events[eventId]
|
||||
.setRedactionEvent(Event.fromJson(eventUpdate.content, room));
|
||||
.setRedactionEvent(Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder));
|
||||
}
|
||||
} else if (eventUpdate.content['status'] == -2) {
|
||||
var i = _findEvent(event_id: eventUpdate.content['event_id']);
|
||||
|
@ -138,18 +138,18 @@ class Timeline {
|
|||
: null);
|
||||
|
||||
if (i < events.length) {
|
||||
events[i] = Event.fromJson(eventUpdate.content, room);
|
||||
events[i] = Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
||||
}
|
||||
} else {
|
||||
Event newEvent;
|
||||
var senderUser = await room.client.store
|
||||
?.getUser(matrixID: eventUpdate.content['sender'], room: room);
|
||||
var senderUser = room.getState('m.room.member', eventUpdate.content['sender'])?.asUser ?? await room.client.database
|
||||
?.getUser(room.client.id, eventUpdate.content['sender'], room);
|
||||
if (senderUser != null) {
|
||||
eventUpdate.content['displayname'] = senderUser.displayName;
|
||||
eventUpdate.content['avatar_url'] = senderUser.avatarUrl.toString();
|
||||
}
|
||||
|
||||
newEvent = Event.fromJson(eventUpdate.content, room);
|
||||
newEvent = Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
||||
|
||||
if (eventUpdate.type == 'history' &&
|
||||
events.indexWhere(
|
||||
|
@ -173,8 +173,7 @@ class Timeline {
|
|||
void sort() {
|
||||
if (sortLock || events.length < 2) return;
|
||||
sortLock = true;
|
||||
events?.sort((a, b) =>
|
||||
b.time.millisecondsSinceEpoch.compareTo(a.time.millisecondsSinceEpoch));
|
||||
events?.sort((a, b) => b.sortOrder - a.sortOrder > 0 ? 1 : -1);
|
||||
sortLock = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import '../client.dart';
|
||||
import '../database/database.dart' show DbUserDeviceKey, DbUserDeviceKeysKey;
|
||||
import '../event.dart';
|
||||
|
||||
class DeviceKeysList {
|
||||
String userId;
|
||||
bool outdated = true;
|
||||
Map<String, DeviceKeys> deviceKeys = {};
|
||||
|
||||
DeviceKeysList.fromDb(DbUserDeviceKey dbEntry, List<DbUserDeviceKeysKey> childEntries) {
|
||||
userId = dbEntry.userId;
|
||||
outdated = dbEntry.outdated;
|
||||
deviceKeys = {};
|
||||
for (final childEntry in childEntries) {
|
||||
deviceKeys[childEntry.deviceId] = DeviceKeys.fromDb(childEntry);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceKeysList.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
outdated = json['outdated'];
|
||||
|
@ -52,7 +63,7 @@ class DeviceKeys {
|
|||
|
||||
Future<void> setVerified(bool newVerified, Client client) {
|
||||
verified = newVerified;
|
||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
||||
return client.database?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId);
|
||||
}
|
||||
|
||||
Future<void> setBlocked(bool newBlocked, Client client) {
|
||||
|
@ -63,7 +74,7 @@ class DeviceKeys {
|
|||
room.clearOutboundGroupSession();
|
||||
}
|
||||
}
|
||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
||||
return client.database?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId);
|
||||
}
|
||||
|
||||
DeviceKeys({
|
||||
|
@ -77,6 +88,22 @@ class DeviceKeys {
|
|||
this.blocked,
|
||||
});
|
||||
|
||||
DeviceKeys.fromDb(DbUserDeviceKeysKey dbEntry) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
userId = dbEntry.userId;
|
||||
deviceId = dbEntry.deviceId;
|
||||
algorithms = content['algorithms'].cast<String>();
|
||||
keys = content['keys'] != null ? Map<String, String>.from(content['keys']) : null;
|
||||
signatures = content['signatures'] != null
|
||||
? Map<String, dynamic>.from(content['signatures'])
|
||||
: null;
|
||||
unsigned = content['unsigned'] != null
|
||||
? Map<String, dynamic>.from(content['unsigned'])
|
||||
: null;
|
||||
verified = dbEntry.verified;
|
||||
blocked = dbEntry.blocked;
|
||||
}
|
||||
|
||||
DeviceKeys.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
deviceId = json['device_id'];
|
||||
|
|
|
@ -16,7 +16,7 @@ class RoomKeyRequest extends ToDeviceEvent {
|
|||
|
||||
Future<void> forwardKey() async {
|
||||
var room = this.room;
|
||||
final session = room.sessionKeys[content['body']['session_id']];
|
||||
final session = room.inboundGroupSessions[content['body']['session_id']];
|
||||
var forwardedKeys = <dynamic>[client.identityKey];
|
||||
for (final key in session.forwardingCurve25519KeyChain) {
|
||||
forwardedKeys.add(key);
|
||||
|
|
|
@ -2,6 +2,9 @@ import 'dart:convert';
|
|||
|
||||
import 'package:olm/olm.dart';
|
||||
|
||||
import '../database/database.dart' show DbInboundGroupSession;
|
||||
import '../event.dart';
|
||||
|
||||
class SessionKey {
|
||||
Map<String, dynamic> content;
|
||||
Map<String, int> indexes;
|
||||
|
@ -14,6 +17,20 @@ class SessionKey {
|
|||
|
||||
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
|
||||
|
||||
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
|
||||
final parsedContent = Event.getMapFromPayload(dbEntry.content);
|
||||
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
|
||||
content = parsedContent != null
|
||||
? Map<String, dynamic>.from(parsedContent)
|
||||
: null;
|
||||
indexes = parsedIndexes != null
|
||||
? Map<String, int>.from(parsedIndexes)
|
||||
: <String, int>{};
|
||||
var newInboundGroupSession = InboundGroupSession();
|
||||
newInboundGroupSession.unpickle(key, dbEntry.pickle);
|
||||
inboundGroupSession = newInboundGroupSession;
|
||||
}
|
||||
|
||||
SessionKey.fromJson(Map<String, dynamic> json, String key) : key = key {
|
||||
content = json['content'] != null
|
||||
? Map<String, dynamic>.from(json['content'])
|
||||
|
|
119
pubspec.lock
119
pubspec.lock
|
@ -1,13 +1,27 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.36.3"
|
||||
version: "0.39.8"
|
||||
analyzer_plugin_fork:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin_fork
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -42,42 +56,42 @@ packages:
|
|||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.2.2"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
version: "0.4.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "2.1.4"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.3.7"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
version: "1.9.0"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "5.1.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -91,7 +105,7 @@ packages:
|
|||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.6.0"
|
||||
version: "7.1.0"
|
||||
canonical_json:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -106,13 +120,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -154,7 +182,7 @@ packages:
|
|||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.7"
|
||||
version: "1.3.6"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -169,13 +197,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.9"
|
||||
front_end:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: front_end
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.18"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -252,14 +273,7 @@ packages:
|
|||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
kernel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: kernel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.18"
|
||||
version: "3.0.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -311,6 +325,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
moor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: moor
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
moor_ffi:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: moor_ffi
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
moor_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: moor_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
multi_server_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -396,7 +431,7 @@ packages:
|
|||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.4"
|
||||
version: "0.1.5"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -404,6 +439,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: recase
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -432,6 +474,13 @@ 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.5"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -453,6 +502,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.5"
|
||||
sqlparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.8.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -473,7 +529,7 @@ packages:
|
|||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.19"
|
||||
version: "1.2.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -481,6 +537,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -515,7 +578,7 @@ packages:
|
|||
name: timing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1+1"
|
||||
version: "0.1.1+2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -14,6 +14,7 @@ dependencies:
|
|||
image: ^2.1.4
|
||||
markdown: ^2.1.3
|
||||
html_unescape: ^1.0.1+3
|
||||
moor: ^3.0.2
|
||||
|
||||
olm:
|
||||
git:
|
||||
|
@ -27,5 +28,7 @@ dependencies:
|
|||
|
||||
dev_dependencies:
|
||||
test: ^1.0.0
|
||||
moor_generator: ^3.0.0
|
||||
build_runner: ^1.5.2
|
||||
pedantic: ^1.9.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL
|
||||
moor_ffi: ^0.5.0
|
||||
|
|
|
@ -39,7 +39,7 @@ import 'package:olm/olm.dart' as olm;
|
|||
import 'package:test/test.dart';
|
||||
|
||||
import 'fake_matrix_api.dart';
|
||||
import 'fake_store.dart';
|
||||
import 'fake_database.dart';
|
||||
|
||||
void main() {
|
||||
Client matrix;
|
||||
|
@ -86,7 +86,6 @@ void main() {
|
|||
});
|
||||
|
||||
expect(matrix.homeserver, null);
|
||||
expect(matrix.matrixVersions, null);
|
||||
|
||||
try {
|
||||
await matrix.checkServer('https://fakeserver.wrongaddress');
|
||||
|
@ -95,8 +94,6 @@ void main() {
|
|||
}
|
||||
await matrix.checkServer('https://fakeserver.notexisting');
|
||||
expect(matrix.homeserver, 'https://fakeserver.notexisting');
|
||||
expect(matrix.matrixVersions,
|
||||
['r0.0.1', 'r0.1.0', 'r0.2.0', 'r0.3.0', 'r0.4.0', 'r0.5.0']);
|
||||
|
||||
final resp = await matrix
|
||||
.jsonRequest(type: HTTPType.POST, action: '/client/r0/login', data: {
|
||||
|
@ -128,7 +125,6 @@ void main() {
|
|||
newHomeserver: matrix.homeserver,
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: resp['device_id'],
|
||||
newMatrixVersions: matrix.matrixVersions,
|
||||
newOlmAccount: pickledOlmAccount,
|
||||
);
|
||||
|
||||
|
@ -160,18 +156,18 @@ void main() {
|
|||
expect(matrix.directChats, matrix.accountData['m.direct'].content);
|
||||
expect(matrix.presences.length, 1);
|
||||
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||
expect(matrix.rooms[1].sessionKeys.length, 1);
|
||||
expect(matrix.rooms[1].inboundGroupSessions.length, 1);
|
||||
expect(
|
||||
matrix
|
||||
.rooms[1]
|
||||
.sessionKeys['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.inboundGroupSessions['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.content['session_key'],
|
||||
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw');
|
||||
if (olmEnabled) {
|
||||
expect(
|
||||
matrix
|
||||
.rooms[1]
|
||||
.sessionKeys['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.inboundGroupSessions['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.inboundGroupSession !=
|
||||
null,
|
||||
true);
|
||||
|
@ -279,7 +275,6 @@ void main() {
|
|||
expect(matrix.userID == null, true);
|
||||
expect(matrix.deviceID == null, true);
|
||||
expect(matrix.deviceName == null, true);
|
||||
expect(matrix.matrixVersions == null, true);
|
||||
expect(matrix.prevBatch == null, true);
|
||||
|
||||
var loginState = await loginStateFuture;
|
||||
|
@ -635,8 +630,7 @@ void main() {
|
|||
test('Test the fake store api', () async {
|
||||
var client1 = Client('testclient', debug: true);
|
||||
client1.httpClient = FakeMatrixApi();
|
||||
var fakeStore = FakeStore(client1, {});
|
||||
client1.storeAPI = fakeStore;
|
||||
client1.database = getDatabase();
|
||||
|
||||
client1.connect(
|
||||
newToken: 'abc123',
|
||||
|
@ -644,14 +638,6 @@ void main() {
|
|||
newHomeserver: 'https://fakeServer.notExisting',
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: 'GHTYAJCE',
|
||||
newMatrixVersions: [
|
||||
'r0.0.1',
|
||||
'r0.1.0',
|
||||
'r0.2.0',
|
||||
'r0.3.0',
|
||||
'r0.4.0',
|
||||
'r0.5.0'
|
||||
],
|
||||
newOlmAccount: pickledOlmAccount,
|
||||
);
|
||||
|
||||
|
@ -669,8 +655,9 @@ void main() {
|
|||
|
||||
var client2 = Client('testclient', debug: true);
|
||||
client2.httpClient = FakeMatrixApi();
|
||||
client2.storeAPI = FakeStore(client2, fakeStore.storeMap);
|
||||
client2.database = client1.database;
|
||||
|
||||
client2.connect();
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
|
||||
expect(client2.isLogged(), true);
|
||||
|
@ -679,11 +666,10 @@ void main() {
|
|||
expect(client2.homeserver, client1.homeserver);
|
||||
expect(client2.deviceID, client1.deviceID);
|
||||
expect(client2.deviceName, client1.deviceName);
|
||||
expect(client2.matrixVersions, client1.matrixVersions);
|
||||
if (client2.encryptionEnabled) {
|
||||
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
|
||||
expect(json.encode(client2.rooms[1].sessionKeys[sessionKey]),
|
||||
json.encode(client1.rooms[1].sessionKeys[sessionKey]));
|
||||
expect(json.encode(client2.rooms[1].inboundGroupSessions[sessionKey]),
|
||||
json.encode(client1.rooms[1].inboundGroupSessions[sessionKey]));
|
||||
expect(client2.rooms[1].id, client1.rooms[1].id);
|
||||
expect(client2.rooms[1].outboundGroupSession.session_key(), sessionKey);
|
||||
}
|
||||
|
|
6
test/fake_database.dart
Normal file
6
test/fake_database.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:moor_ffi/moor_ffi.dart' as moor;
|
||||
|
||||
Database getDatabase() {
|
||||
return Database(moor.VmDatabase.memory());
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
class FakeStore implements StoreAPI {
|
||||
/// Whether this is a simple store which only stores the client credentials and
|
||||
/// end to end encryption stuff or the whole sync payloads.
|
||||
@override
|
||||
final bool extended = false;
|
||||
|
||||
Map<String, dynamic> storeMap = {};
|
||||
|
||||
/// Link back to the client.
|
||||
@override
|
||||
Client client;
|
||||
|
||||
FakeStore(this.client, this.storeMap) {
|
||||
_init();
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final credentialsStr = await getItem(client.clientName);
|
||||
|
||||
if (credentialsStr == null || credentialsStr.isEmpty) {
|
||||
client.onLoginStateChanged.add(LoginState.loggedOut);
|
||||
return;
|
||||
}
|
||||
print('[Matrix] Restoring account credentials');
|
||||
final Map<String, dynamic> credentials = json.decode(credentialsStr);
|
||||
client.connect(
|
||||
newDeviceID: credentials['deviceID'],
|
||||
newDeviceName: credentials['deviceName'],
|
||||
newHomeserver: credentials['homeserver'],
|
||||
newMatrixVersions: List<String>.from(credentials['matrixVersions']),
|
||||
newToken: credentials['token'],
|
||||
newUserID: credentials['userID'],
|
||||
newPrevBatch: credentials['prev_batch'],
|
||||
newOlmAccount: credentials['olmAccount'],
|
||||
);
|
||||
}
|
||||
|
||||
/// Will be automatically called when the client is logged in successfully.
|
||||
@override
|
||||
Future<void> storeClient() async {
|
||||
final credentials = {
|
||||
'deviceID': client.deviceID,
|
||||
'deviceName': client.deviceName,
|
||||
'homeserver': client.homeserver,
|
||||
'matrixVersions': client.matrixVersions,
|
||||
'token': client.accessToken,
|
||||
'userID': client.userID,
|
||||
'olmAccount': client.pickledOlmAccount,
|
||||
};
|
||||
await setItem(client.clientName, json.encode(credentials));
|
||||
return;
|
||||
}
|
||||
|
||||
/// Clears all tables from the database.
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
storeMap = {};
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<dynamic> getItem(String key) async {
|
||||
return storeMap[key];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setItem(String key, String value) async {
|
||||
storeMap[key] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
String get _UserDeviceKeysKey => '${client.clientName}.user_device_keys';
|
||||
|
||||
@override
|
||||
Future<Map<String, DeviceKeysList>> getUserDeviceKeys() async {
|
||||
final deviceKeysListString = await getItem(_UserDeviceKeysKey);
|
||||
if (deviceKeysListString == null) return {};
|
||||
Map<String, dynamic> rawUserDeviceKeys = json.decode(deviceKeysListString);
|
||||
var userDeviceKeys = <String, DeviceKeysList>{};
|
||||
for (final entry in rawUserDeviceKeys.entries) {
|
||||
userDeviceKeys[entry.key] = DeviceKeysList.fromJson(entry.value);
|
||||
}
|
||||
return userDeviceKeys;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> storeUserDeviceKeys(
|
||||
Map<String, DeviceKeysList> userDeviceKeys) async {
|
||||
await setItem(_UserDeviceKeysKey, json.encode(userDeviceKeys));
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import 'package:famedlysdk/famedlysdk.dart';
|
|||
import 'package:test/test.dart';
|
||||
|
||||
import 'fake_matrix_api.dart';
|
||||
import 'fake_store.dart';
|
||||
import 'fake_database.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to device keys
|
||||
|
@ -53,13 +53,13 @@ void main() {
|
|||
|
||||
var matrix = Client('testclient', debug: true);
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
matrix.storeAPI = FakeStore(matrix, {});
|
||||
matrix.database = getDatabase();
|
||||
await matrix.checkServer('https://fakeServer.notExisting');
|
||||
await matrix.login('test', '1234');
|
||||
var room = matrix.getRoomById('!726s6s6q:example.com');
|
||||
if (matrix.encryptionEnabled) {
|
||||
await room.createOutboundGroupSession();
|
||||
rawJson['content']['body']['session_id'] = room.sessionKeys.keys.first;
|
||||
rawJson['content']['body']['session_id'] = room.inboundGroupSessions.keys.first;
|
||||
|
||||
var roomKeyRequest = RoomKeyRequest.fromToDeviceEvent(
|
||||
ToDeviceEvent.fromJson(rawJson), matrix);
|
||||
|
|
|
@ -26,6 +26,7 @@ import 'package:famedlysdk/src/event.dart';
|
|||
import 'package:famedlysdk/src/room.dart';
|
||||
import 'package:famedlysdk/src/user.dart';
|
||||
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
import 'package:famedlysdk/src/database/database.dart' show DbRoom, DbRoomState, DbRoomAccountData;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'fake_matrix_api.dart';
|
||||
|
@ -62,44 +63,50 @@ void main() {
|
|||
'@charley:example.org'
|
||||
];
|
||||
|
||||
var jsonObj = <String, dynamic>{
|
||||
'room_id': id,
|
||||
'membership': membership.toString().split('.').last,
|
||||
'avatar_url': '',
|
||||
'notification_count': notificationCount,
|
||||
'highlight_count': highlightCount,
|
||||
'prev_batch': '',
|
||||
'joined_member_count': notificationCount,
|
||||
'invited_member_count': notificationCount,
|
||||
'heroes': heroes.join(','),
|
||||
};
|
||||
var dbRoom = DbRoom(
|
||||
clientId: 1,
|
||||
roomId: id,
|
||||
membership: membership.toString().split('.').last,
|
||||
highlightCount: highlightCount,
|
||||
notificationCount: notificationCount,
|
||||
prevBatch: '',
|
||||
joinedMemberCount: notificationCount,
|
||||
invitedMemberCount: notificationCount,
|
||||
newestSortOrder: 0.0,
|
||||
oldestSortOrder: 0.0,
|
||||
heroes: heroes.join(','),
|
||||
);
|
||||
|
||||
Function states = () async => [
|
||||
{
|
||||
'content': {'join_rule': 'public'},
|
||||
'event_id': '143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': id,
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.join_rules',
|
||||
'unsigned': {'age': 1234}
|
||||
}
|
||||
];
|
||||
var states = [
|
||||
DbRoomState(
|
||||
clientId: 1,
|
||||
eventId: '143273582443PhrSn:example.org',
|
||||
roomId: id,
|
||||
sortOrder: 0.0,
|
||||
originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653),
|
||||
sender: '@example:example.org',
|
||||
type: 'm.room.join_rules',
|
||||
unsigned: '{"age": 1234}',
|
||||
content: '{"join_rule": "public"}',
|
||||
prevContent: '',
|
||||
stateKey: '',
|
||||
),
|
||||
];
|
||||
|
||||
Function roomAccountData = () async => [
|
||||
{
|
||||
'content': {'foo': 'bar'},
|
||||
'room_id': id,
|
||||
'type': 'com.test.foo'
|
||||
}
|
||||
];
|
||||
var roomAccountData = [
|
||||
DbRoomAccountData(
|
||||
clientId: 1,
|
||||
type: 'com.test.foo',
|
||||
roomId: id,
|
||||
content: '{"foo": "bar"}',
|
||||
),
|
||||
];
|
||||
|
||||
room = await Room.getRoomFromTableRow(
|
||||
jsonObj,
|
||||
dbRoom,
|
||||
matrix,
|
||||
states: states(),
|
||||
roomAccountData: roomAccountData(),
|
||||
states: states,
|
||||
roomAccountData: roomAccountData,
|
||||
);
|
||||
|
||||
expect(room.id, id);
|
||||
|
@ -390,14 +397,14 @@ void main() {
|
|||
expect(room.outboundGroupSession != null, true);
|
||||
expect(room.outboundGroupSession.session_id().isNotEmpty, true);
|
||||
expect(
|
||||
room.sessionKeys.containsKey(room.outboundGroupSession.session_id()),
|
||||
room.inboundGroupSessions.containsKey(room.outboundGroupSession.session_id()),
|
||||
true);
|
||||
expect(
|
||||
room.sessionKeys[room.outboundGroupSession.session_id()]
|
||||
room.inboundGroupSessions[room.outboundGroupSession.session_id()]
|
||||
.content['session_key'],
|
||||
room.outboundGroupSession.session_key());
|
||||
expect(
|
||||
room.sessionKeys[room.outboundGroupSession.session_id()].indexes
|
||||
room.inboundGroupSessions[room.outboundGroupSession.session_id()].indexes
|
||||
.length,
|
||||
0);
|
||||
});
|
||||
|
|
|
@ -65,7 +65,8 @@ void main() {
|
|||
'status': 2,
|
||||
'event_id': '1',
|
||||
'origin_server_ts': testTimeStamp
|
||||
}));
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
|
@ -78,7 +79,8 @@ void main() {
|
|||
'status': 2,
|
||||
'event_id': '2',
|
||||
'origin_server_ts': testTimeStamp - 1000
|
||||
}));
|
||||
},
|
||||
sortOrder: room.oldSortOrder));
|
||||
|
||||
expect(timeline.sub != null, true);
|
||||
|
||||
|
@ -125,7 +127,8 @@ void main() {
|
|||
'redacts': '2',
|
||||
'event_id': '3',
|
||||
'origin_server_ts': testTimeStamp + 1000
|
||||
}));
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
|
@ -159,7 +162,8 @@ void main() {
|
|||
'event_id': '42',
|
||||
'unsigned': {'transaction_id': '1234'},
|
||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch
|
||||
}));
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
|
@ -182,7 +186,8 @@ void main() {
|
|||
'status': 0,
|
||||
'event_id': 'abc',
|
||||
'origin_server_ts': testTimeStamp
|
||||
}));
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
await room.sendTextEvent('test', txid: 'errortxid');
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
@ -230,9 +235,9 @@ void main() {
|
|||
|
||||
expect(updateCount, 20);
|
||||
expect(timeline.events.length, 9);
|
||||
expect(timeline.events[6].eventId, '1143273582443PhrSn:example.org');
|
||||
expect(timeline.events[6].eventId, '3143273582443PhrSn:example.org');
|
||||
expect(timeline.events[7].eventId, '2143273582443PhrSn:example.org');
|
||||
expect(timeline.events[8].eventId, '3143273582443PhrSn:example.org');
|
||||
expect(timeline.events[8].eventId, '1143273582443PhrSn:example.org');
|
||||
expect(room.prev_batch, 't47409-4357353_219380_26003_2265');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import '../test/fake_store.dart';
|
||||
import '../test/fake_database.dart';
|
||||
|
||||
void main() => test();
|
||||
|
||||
|
@ -18,14 +18,14 @@ const String testMessage6 = 'Hello mars';
|
|||
void test() async {
|
||||
print('++++ Login $testUserA ++++');
|
||||
var testClientA = Client('TestClient', debug: false);
|
||||
testClientA.storeAPI = FakeStore(testClientA, <String, dynamic>{});
|
||||
testClientA.database = getDatabase();
|
||||
await testClientA.checkServer(homeserver);
|
||||
await testClientA.login(testUserA, testPasswordA);
|
||||
assert(testClientA.encryptionEnabled);
|
||||
|
||||
print('++++ Login $testUserB ++++');
|
||||
var testClientB = Client('TestClient', debug: false);
|
||||
testClientB.storeAPI = FakeStore(testClientB, <String, dynamic>{});
|
||||
testClientB.database = getDatabase();
|
||||
await testClientB.checkServer(homeserver);
|
||||
await testClientB.login(testUserB, testPasswordA);
|
||||
assert(testClientB.encryptionEnabled);
|
||||
|
@ -128,12 +128,12 @@ void test() async {
|
|||
await Future.delayed(Duration(seconds: 5));
|
||||
assert(room.outboundGroupSession != null);
|
||||
var currentSessionIdA = room.outboundGroupSession.session_id();
|
||||
assert(room.sessionKeys.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(room.inboundGroupSessions.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(testClientA.olmSessions[testClientB.identityKey].length == 1);
|
||||
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
||||
assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() ==
|
||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(room.lastMessage == testMessage);
|
||||
assert(inviteRoom.lastMessage == testMessage);
|
||||
|
@ -149,7 +149,7 @@ void test() async {
|
|||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||
|
||||
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(room.lastMessage == testMessage2);
|
||||
assert(inviteRoom.lastMessage == testMessage2);
|
||||
|
@ -163,9 +163,9 @@ void test() async {
|
|||
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
||||
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
||||
assert(inviteRoom.outboundGroupSession != null);
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
||||
assert(room.sessionKeys
|
||||
assert(room.inboundGroupSessions
|
||||
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
||||
assert(inviteRoom.lastMessage == testMessage3);
|
||||
assert(room.lastMessage == testMessage3);
|
||||
|
@ -174,7 +174,7 @@ void test() async {
|
|||
|
||||
print('++++ Login $testUserB in another client ++++');
|
||||
var testClientC = Client('TestClient', debug: false);
|
||||
testClientC.storeAPI = FakeStore(testClientC, <String, dynamic>{});
|
||||
testClientC.database = getDatabase();
|
||||
await testClientC.checkServer(homeserver);
|
||||
await testClientC.login(testUserB, testPasswordA);
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
|
@ -193,7 +193,7 @@ void test() async {
|
|||
testClientC.olmSessions[testClientA.identityKey].first.session_id());
|
||||
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
||||
currentSessionIdA = room.outboundGroupSession.session_id();
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(room.lastMessage == testMessage4);
|
||||
assert(inviteRoom.lastMessage == testMessage4);
|
||||
|
@ -216,7 +216,7 @@ void test() async {
|
|||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
||||
currentSessionIdA = room.outboundGroupSession.session_id();
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(room.outboundGroupSession.session_id()));
|
||||
assert(room.lastMessage == testMessage6);
|
||||
assert(inviteRoom.lastMessage == testMessage6);
|
||||
|
@ -224,21 +224,22 @@ void test() async {
|
|||
"++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++");
|
||||
|
||||
print('++++ ($testUserA) Restore user ++++');
|
||||
FakeStore clientAStore = testClientA.storeAPI;
|
||||
final clientADatabase = testClientA.database;
|
||||
testClientA = null;
|
||||
testClientA = Client('TestClient', debug: false);
|
||||
testClientA.storeAPI = FakeStore(testClientA, clientAStore.storeMap);
|
||||
testClientA.database = clientADatabase;
|
||||
testClientA.connect();
|
||||
await Future.delayed(Duration(seconds: 3));
|
||||
var restoredRoom = testClientA.rooms.first;
|
||||
assert(room != null);
|
||||
assert(restoredRoom.id == room.id);
|
||||
assert(restoredRoom.outboundGroupSession.session_id() ==
|
||||
room.outboundGroupSession.session_id());
|
||||
assert(restoredRoom.sessionKeys.length == 4);
|
||||
assert(restoredRoom.sessionKeys.length == room.sessionKeys.length);
|
||||
for (var i = 0; i < restoredRoom.sessionKeys.length; i++) {
|
||||
assert(restoredRoom.sessionKeys.keys.toList()[i] ==
|
||||
room.sessionKeys.keys.toList()[i]);
|
||||
assert(restoredRoom.inboundGroupSessions.length == 4);
|
||||
assert(restoredRoom.inboundGroupSessions.length == room.inboundGroupSessions.length);
|
||||
for (var i = 0; i < restoredRoom.inboundGroupSessions.length; i++) {
|
||||
assert(restoredRoom.inboundGroupSessions.keys.toList()[i] ==
|
||||
room.inboundGroupSessions.keys.toList()[i]);
|
||||
}
|
||||
assert(testClientA.olmSessions[testClientB.identityKey].length == 1);
|
||||
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
||||
|
@ -253,7 +254,7 @@ void test() async {
|
|||
assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() ==
|
||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||
/*assert(restoredRoom.outboundGroupSession.session_id() == currentSessionIdA);
|
||||
assert(inviteRoom.sessionKeys
|
||||
assert(inviteRoom.inboundGroupSessions
|
||||
.containsKey(restoredRoom.outboundGroupSession.session_id()));*/
|
||||
assert(restoredRoom.lastMessage == testMessage5);
|
||||
assert(inviteRoom.lastMessage == testMessage5);
|
||||
|
|
Loading…
Reference in a new issue