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/
|
**/doc/api/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
.flutter-plugins
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
.packages
|
.packages
|
||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.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
|
- 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 install -y ./dart.deb
|
||||||
- apt update
|
- 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/
|
- ln -s /usr/lib/dart/bin/pub /usr/bin/
|
||||||
- useradd -m test
|
- useradd -m test
|
||||||
- chown -R 'test:' '.'
|
- chown -R 'test:' '.'
|
||||||
|
@ -33,7 +33,7 @@ coverage_without_olm:
|
||||||
dependencies: []
|
dependencies: []
|
||||||
script:
|
script:
|
||||||
- apt update
|
- 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
|
- 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 install -y ./dart.deb
|
||||||
- ln -s /usr/lib/dart/bin/pub /usr/bin/
|
- 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/presence.dart';
|
||||||
export 'package:famedlysdk/src/room.dart';
|
export 'package:famedlysdk/src/room.dart';
|
||||||
export 'package:famedlysdk/src/room_account_data.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/timeline.dart';
|
||||||
export 'package:famedlysdk/src/user.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 'package:famedlysdk/famedlysdk.dart';
|
||||||
|
import './database/database.dart' show DbAccountData;
|
||||||
|
|
||||||
/// The global private data created by this user.
|
/// The global private data created by this user.
|
||||||
class AccountData {
|
class AccountData {
|
||||||
|
@ -38,4 +39,10 @@ class AccountData {
|
||||||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||||
return AccountData(content: content, typeKey: jsonPayload['type']);
|
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/account_data.dart';
|
||||||
import 'package:famedlysdk/src/presence.dart';
|
import 'package:famedlysdk/src/presence.dart';
|
||||||
import 'package:famedlysdk/src/room.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/sync/user_update.dart';
|
||||||
import 'package:famedlysdk/src/utils/device_keys_list.dart';
|
import 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||||
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||||
|
@ -54,6 +53,7 @@ import 'sync/user_update.dart';
|
||||||
import 'user.dart';
|
import 'user.dart';
|
||||||
import 'utils/matrix_exception.dart';
|
import 'utils/matrix_exception.dart';
|
||||||
import 'utils/profile.dart';
|
import 'utils/profile.dart';
|
||||||
|
import 'database/database.dart' show Database;
|
||||||
import 'utils/pusher.dart';
|
import 'utils/pusher.dart';
|
||||||
|
|
||||||
typedef RoomSorter = int Function(Room a, Room b);
|
typedef RoomSorter = int Function(Room a, Room b);
|
||||||
|
@ -70,12 +70,12 @@ class Client {
|
||||||
@deprecated
|
@deprecated
|
||||||
Client get connection => this;
|
Client get connection => this;
|
||||||
|
|
||||||
/// Optional persistent store for all data.
|
int _id;
|
||||||
ExtendedStoreAPI get store => (storeAPI?.extended ?? false) ? storeAPI : null;
|
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) {
|
onLoginStateChanged.stream.listen((loginState) {
|
||||||
print('LoginState: ${loginState.toString()}');
|
print('LoginState: ${loginState.toString()}');
|
||||||
});
|
});
|
||||||
|
@ -111,10 +111,6 @@ class Client {
|
||||||
String get deviceName => _deviceName;
|
String get deviceName => _deviceName;
|
||||||
String _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.
|
/// Returns the current login state.
|
||||||
bool isLogged() => accessToken != null;
|
bool isLogged() => accessToken != null;
|
||||||
|
|
||||||
|
@ -208,7 +204,7 @@ class Client {
|
||||||
|
|
||||||
/// Checks the supported versions of the Matrix protocol and the supported
|
/// Checks the supported versions of the Matrix protocol and the supported
|
||||||
/// login types. Returns false if the server is not compatible with the
|
/// login types. Returns false if the server is not compatible with the
|
||||||
/// client. Automatically sets [matrixVersions].
|
/// client.
|
||||||
/// Throws FormatException, TimeoutException and MatrixException on error.
|
/// Throws FormatException, TimeoutException and MatrixException on error.
|
||||||
Future<bool> checkServer(serverUrl) async {
|
Future<bool> checkServer(serverUrl) async {
|
||||||
try {
|
try {
|
||||||
|
@ -226,8 +222,6 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_matrixVersions = versions;
|
|
||||||
|
|
||||||
final loginResp =
|
final loginResp =
|
||||||
await jsonRequest(type: HTTPType.GET, action: '/client/r0/login');
|
await jsonRequest(type: HTTPType.GET, action: '/client/r0/login');
|
||||||
|
|
||||||
|
@ -243,7 +237,7 @@ class Client {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
_homeserver = _matrixVersions = null;
|
_homeserver = null;
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,8 +286,7 @@ class Client {
|
||||||
newUserID: response['user_id'],
|
newUserID: response['user_id'],
|
||||||
newHomeserver: homeserver,
|
newHomeserver: homeserver,
|
||||||
newDeviceName: initialDeviceDisplayName ?? '',
|
newDeviceName: initialDeviceDisplayName ?? '',
|
||||||
newDeviceID: response['device_id'],
|
newDeviceID: response['device_id']);
|
||||||
newMatrixVersions: matrixVersions);
|
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -334,7 +327,6 @@ class Client {
|
||||||
newHomeserver: homeserver,
|
newHomeserver: homeserver,
|
||||||
newDeviceName: initialDeviceDisplayName ?? '',
|
newDeviceName: initialDeviceDisplayName ?? '',
|
||||||
newDeviceID: loginResp['device_id'],
|
newDeviceID: loginResp['device_id'],
|
||||||
newMatrixVersions: matrixVersions,
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -676,20 +668,39 @@ class Client {
|
||||||
String newUserID,
|
String newUserID,
|
||||||
String newDeviceName,
|
String newDeviceName,
|
||||||
String newDeviceID,
|
String newDeviceID,
|
||||||
List<String> newMatrixVersions,
|
|
||||||
String newPrevBatch,
|
String newPrevBatch,
|
||||||
String newOlmAccount,
|
String newOlmAccount,
|
||||||
}) async {
|
}) async {
|
||||||
_accessToken = newToken;
|
String olmAccount;
|
||||||
_homeserver = newHomeserver;
|
if (database != null) {
|
||||||
_userID = newUserID;
|
final account = await database.getClient(clientName);
|
||||||
_deviceID = newDeviceID;
|
if (account != null) {
|
||||||
_deviceName = newDeviceName;
|
_id = account.clientId;
|
||||||
_matrixVersions = newMatrixVersions;
|
_homeserver = account.homeserverUrl;
|
||||||
prevBatch = newPrevBatch;
|
_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.
|
// Try to create a new olm account or restore a previous one.
|
||||||
if (newOlmAccount == null) {
|
if (olmAccount == null) {
|
||||||
try {
|
try {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
_olmAccount = olm.Account();
|
_olmAccount = olm.Account();
|
||||||
|
@ -704,39 +715,30 @@ class Client {
|
||||||
try {
|
try {
|
||||||
await olm.init();
|
await olm.init();
|
||||||
_olmAccount = olm.Account();
|
_olmAccount = olm.Account();
|
||||||
_olmAccount.unpickle(userID, newOlmAccount);
|
_olmAccount.unpickle(userID, olmAccount);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
_olmAccount = null;
|
_olmAccount = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storeAPI != null) {
|
if (database != null) {
|
||||||
await storeAPI.storeClient();
|
if (id != null) {
|
||||||
_userDeviceKeys = await storeAPI.getUserDeviceKeys();
|
await database.updateClient(
|
||||||
final String olmSessionPickleString =
|
_homeserver, _accessToken, _userID, _deviceID,
|
||||||
await storeAPI.getItem('/clients/$userID/olm-sessions');
|
_deviceName, prevBatch, pickledOlmAccount, id,
|
||||||
if (olmSessionPickleString != null) {
|
);
|
||||||
final Map<String, dynamic> pickleMap =
|
} else {
|
||||||
json.decode(olmSessionPickleString);
|
_id = await database.insertClient(
|
||||||
for (var entry in pickleMap.entries) {
|
clientName, _homeserver, _accessToken, _userID, _deviceID,
|
||||||
for (String pickle in entry.value) {
|
_deviceName, prevBatch, pickledOlmAccount,
|
||||||
_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();
|
|
||||||
}
|
}
|
||||||
|
_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);
|
_userEventSub ??= onUserEvent.stream.listen(handleUserUpdate);
|
||||||
|
@ -755,14 +757,14 @@ class Client {
|
||||||
});
|
});
|
||||||
rooms.forEach((Room room) {
|
rooms.forEach((Room room) {
|
||||||
room.clearOutboundGroupSession(wipe: true);
|
room.clearOutboundGroupSession(wipe: true);
|
||||||
room.sessionKeys.values.forEach((SessionKey sessionKey) {
|
room.inboundGroupSessions.values.forEach((SessionKey sessionKey) {
|
||||||
sessionKey.inboundGroupSession?.free();
|
sessionKey.inboundGroupSession?.free();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
_olmAccount?.free();
|
_olmAccount?.free();
|
||||||
storeAPI?.clear();
|
database?.clear(id);
|
||||||
_accessToken = _homeserver =
|
_id = _accessToken = _homeserver =
|
||||||
_userID = _deviceID = _deviceName = _matrixVersions = prevBatch = null;
|
_userID = _deviceID = _deviceName = prevBatch = null;
|
||||||
_rooms = [];
|
_rooms = [];
|
||||||
onLoginStateChanged.add(LoginState.loggedOut);
|
onLoginStateChanged.add(LoginState.loggedOut);
|
||||||
}
|
}
|
||||||
|
@ -858,6 +860,7 @@ class Client {
|
||||||
var exception = MatrixException(resp);
|
var exception = MatrixException(resp);
|
||||||
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
||||||
// The token is no longer valid. Need to sign off....
|
// The token is no longer valid. Need to sign off....
|
||||||
|
// TODO: add a way to export keys prior logout?
|
||||||
onError.add(exception);
|
onError.add(exception);
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
@ -926,14 +929,15 @@ class Client {
|
||||||
final syncResp = await _syncRequest;
|
final syncResp = await _syncRequest;
|
||||||
if (hash != _syncRequest.hashCode) return;
|
if (hash != _syncRequest.hashCode) return;
|
||||||
_timeoutFactor = 1;
|
_timeoutFactor = 1;
|
||||||
if (store != null) {
|
final futures = handleSync(syncResp);
|
||||||
await store.transaction(() {
|
await database?.transaction(() async {
|
||||||
handleSync(syncResp);
|
for (final f in futures) {
|
||||||
store.storePrevBatch(syncResp['next_batch']);
|
await f();
|
||||||
});
|
}
|
||||||
} else {
|
if (prevBatch != syncResp['next_batch']) {
|
||||||
await handleSync(syncResp);
|
await database.storePrevBatch(syncResp['next_batch'], id);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (prevBatch == null) {
|
if (prevBatch == null) {
|
||||||
onFirstSync.add(true);
|
onFirstSync.add(true);
|
||||||
prevBatch = syncResp['next_batch'];
|
prevBatch = syncResp['next_batch'];
|
||||||
|
@ -946,37 +950,39 @@ class Client {
|
||||||
onError.add(exception);
|
onError.add(exception);
|
||||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
|
print('Error during processing events: ' + exception.toString());
|
||||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use this method only for testing utilities!
|
/// 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> &&
|
if (sync['to_device'] is Map<String, dynamic> &&
|
||||||
sync['to_device']['events'] is List<dynamic>) {
|
sync['to_device']['events'] is List<dynamic>) {
|
||||||
_handleToDeviceEvents(sync['to_device']['events']);
|
_handleToDeviceEvents(sync['to_device']['events']);
|
||||||
}
|
}
|
||||||
if (sync['rooms'] is Map<String, dynamic>) {
|
if (sync['rooms'] is Map<String, dynamic>) {
|
||||||
if (sync['rooms']['join'] 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>) {
|
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>) {
|
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> &&
|
if (sync['presence'] is Map<String, dynamic> &&
|
||||||
sync['presence']['events'] is List<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> &&
|
if (sync['account_data'] is Map<String, dynamic> &&
|
||||||
sync['account_data']['events'] is List<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>) {
|
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>) {
|
if (sync['device_one_time_keys_count'] is Map<String, dynamic>) {
|
||||||
_handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']);
|
_handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']);
|
||||||
|
@ -988,6 +994,7 @@ class Client {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
onSync.add(sync);
|
onSync.add(sync);
|
||||||
|
return dbActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDeviceOneTimeKeysCount(
|
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) {
|
if (deviceLists['changed'] is List) {
|
||||||
for (final userId in deviceLists['changed']) {
|
for (final userId in deviceLists['changed']) {
|
||||||
if (_userDeviceKeys.containsKey(userId)) {
|
if (_userDeviceKeys.containsKey(userId)) {
|
||||||
_userDeviceKeys[userId].outdated = true;
|
_userDeviceKeys[userId].outdated = true;
|
||||||
|
if (database != null) {
|
||||||
|
dbActions.add(() => database.storeUserDeviceKeysInfo(id, userId, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final userId in deviceLists['left']) {
|
for (final userId in deviceLists['left']) {
|
||||||
|
@ -1046,8 +1056,8 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRooms(Map<String, dynamic> rooms, Membership membership) {
|
void _handleRooms(Map<String, dynamic> rooms, Membership membership, List<Future<dynamic> Function()> dbActions) {
|
||||||
rooms.forEach((String id, dynamic room) async {
|
rooms.forEach((String id, dynamic room) {
|
||||||
// calculate the notification counts, the limitedTimeline and prevbatch
|
// calculate the notification counts, the limitedTimeline and prevbatch
|
||||||
num highlight_count = 0;
|
num highlight_count = 0;
|
||||||
num notification_count = 0;
|
num notification_count = 0;
|
||||||
|
@ -1089,40 +1099,55 @@ class Client {
|
||||||
summary: summary,
|
summary: summary,
|
||||||
);
|
);
|
||||||
_updateRoomsByRoomUpdate(update);
|
_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);
|
onRoomUpdate.add(update);
|
||||||
|
|
||||||
|
var handledEvents = false;
|
||||||
/// Handle now all room events and save them in the database
|
/// Handle now all room events and save them in the database
|
||||||
if (room['state'] is Map<String, dynamic> &&
|
if (room['state'] is Map<String, dynamic> &&
|
||||||
room['state']['events'] is List<dynamic>) {
|
room['state']['events'] is List<dynamic> &&
|
||||||
_handleRoomEvents(id, room['state']['events'], 'state');
|
room['state']['events'].isNotEmpty) {
|
||||||
|
_handleRoomEvents(id, room['state']['events'], 'state', dbActions);
|
||||||
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room['invite_state'] is Map<String, dynamic> &&
|
if (room['invite_state'] is Map<String, dynamic> &&
|
||||||
room['invite_state']['events'] is List<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> &&
|
if (room['timeline'] is Map<String, dynamic> &&
|
||||||
room['timeline']['events'] is List<dynamic>) {
|
room['timeline']['events'] is List<dynamic> &&
|
||||||
_handleRoomEvents(id, room['timeline']['events'], 'timeline');
|
room['timeline']['events'].isNotEmpty) {
|
||||||
|
_handleRoomEvents(id, room['timeline']['events'], 'timeline', dbActions);
|
||||||
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room['ephemeral'] is Map<String, dynamic> &&
|
if (room['ephemeral'] is Map<String, dynamic> &&
|
||||||
room['ephemeral']['events'] is List<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> &&
|
if (room['account_data'] is Map<String, dynamic> &&
|
||||||
room['account_data']['events'] is List<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++) {
|
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
|
// Receipt events are deltas between two states. We will create a
|
||||||
// fake room account data event for this and store the difference
|
// fake room account data event for this and store the difference
|
||||||
|
@ -1142,12 +1167,12 @@ class Client {
|
||||||
final mxid = userTimestampMapEntry.key;
|
final mxid = userTimestampMapEntry.key;
|
||||||
|
|
||||||
// Remove previous receipt event from this user
|
// Remove previous receipt event from this user
|
||||||
for (var entry in receiptStateContent.entries) {
|
if (
|
||||||
if (entry.value['m.read'] is Map<String, dynamic> &&
|
receiptStateContent[eventID] is Map<String, dynamic> &&
|
||||||
entry.value['m.read'].containsKey(mxid)) {
|
receiptStateContent[eventID]['m.read'] is Map<String, dynamic> &&
|
||||||
entry.value['m.read'].remove(mxid);
|
receiptStateContent[eventID]['m.read'].containsKey(mxid)
|
||||||
break;
|
) {
|
||||||
}
|
receiptStateContent[eventID]['m.read'].remove(mxid);
|
||||||
}
|
}
|
||||||
if (userTimestampMap[mxid] is Map<String, dynamic> &&
|
if (userTimestampMap[mxid] is Map<String, dynamic> &&
|
||||||
userTimestampMap[mxid].containsKey('ts')) {
|
userTimestampMap[mxid].containsKey('ts')) {
|
||||||
|
@ -1160,18 +1185,18 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events[i]['content'] = receiptStateContent;
|
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++) {
|
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++) {
|
for (var i = 0; i < events.length; i++) {
|
||||||
if (events[i]['type'] is String &&
|
if (events[i]['type'] is String &&
|
||||||
events[i]['content'] is Map<String, dynamic>) {
|
events[i]['content'] is Map<String, dynamic>) {
|
||||||
|
@ -1180,42 +1205,51 @@ class Client {
|
||||||
type: type,
|
type: type,
|
||||||
content: events[i],
|
content: events[i],
|
||||||
);
|
);
|
||||||
store?.storeUserEventUpdate(update);
|
if (database != null) {
|
||||||
|
dbActions.add(() => database.storeUserEventUpdate(id, update));
|
||||||
|
}
|
||||||
onUserEvent.add(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>) {
|
if (event['type'] is String && event['content'] is Map<String, dynamic>) {
|
||||||
// The client must ignore any new m.room.encryption event to prevent
|
// The client must ignore any new m.room.encryption event to prevent
|
||||||
// man-in-the-middle attacks!
|
// man-in-the-middle attacks!
|
||||||
if (event['type'] == 'm.room.encryption' &&
|
final room = getRoomById(roomID);
|
||||||
getRoomById(roomID).encrypted) {
|
if (room == null || (event['type'] == 'm.room.encryption' &&
|
||||||
|
room.encrypted)) {
|
||||||
return;
|
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(
|
var update = EventUpdate(
|
||||||
eventType: event['type'],
|
eventType: event['type'],
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
type: type,
|
type: type,
|
||||||
content: event,
|
content: event,
|
||||||
|
sortOrder: sortOrder,
|
||||||
);
|
);
|
||||||
if (event['type'] == 'm.room.encrypted') {
|
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);
|
_updateRoomsByEventUpdate(update);
|
||||||
onEvent.add(update);
|
onEvent.add(update);
|
||||||
|
|
||||||
if (event['type'] == 'm.call.invite') {
|
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') {
|
} 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') {
|
} 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') {
|
} 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' ||
|
if (eventUpdate.type == 'timeline' ||
|
||||||
eventUpdate.type == 'state' ||
|
eventUpdate.type == 'state' ||
|
||||||
eventUpdate.type == 'invite_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) {
|
if (stateEvent.type == EventTypes.Redaction) {
|
||||||
final String redacts = eventUpdate.content['redacts'];
|
final String redacts = eventUpdate.content['redacts'];
|
||||||
rooms[j].states.states.forEach(
|
rooms[j].states.states.forEach(
|
||||||
|
@ -1337,6 +1371,7 @@ class Client {
|
||||||
var room = getRoomById(roomId);
|
var room = getRoomById(roomId);
|
||||||
if (room == null && addToPendingIfNotFound) {
|
if (room == null && addToPendingIfNotFound) {
|
||||||
_pendingToDeviceEvents.add(toDeviceEvent);
|
_pendingToDeviceEvents.add(toDeviceEvent);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
final String sessionId = toDeviceEvent.content['session_id'];
|
final String sessionId = toDeviceEvent.content['session_id'];
|
||||||
if (toDeviceEvent.type == 'm.room_key' &&
|
if (toDeviceEvent.type == 'm.room_key' &&
|
||||||
|
@ -1349,7 +1384,7 @@ class Client {
|
||||||
.deviceKeys[toDeviceEvent.content['requesting_device_id']]
|
.deviceKeys[toDeviceEvent.content['requesting_device_id']]
|
||||||
.ed25519Key;
|
.ed25519Key;
|
||||||
}
|
}
|
||||||
room.setSessionKey(
|
room.setInboundGroupSession(
|
||||||
sessionId,
|
sessionId,
|
||||||
toDeviceEvent.content,
|
toDeviceEvent.content,
|
||||||
forwarded: toDeviceEvent.type == 'm.forwarded_room_key',
|
forwarded: toDeviceEvent.type == 'm.forwarded_room_key',
|
||||||
|
@ -1379,7 +1414,7 @@ class Client {
|
||||||
.containsKey(toDeviceEvent.content['requesting_device_id'])) {
|
.containsKey(toDeviceEvent.content['requesting_device_id'])) {
|
||||||
deviceKeys = userDeviceKeys[toDeviceEvent.sender]
|
deviceKeys = userDeviceKeys[toDeviceEvent.sender]
|
||||||
.deviceKeys[toDeviceEvent.content['requesting_device_id']];
|
.deviceKeys[toDeviceEvent.content['requesting_device_id']];
|
||||||
if (room.sessionKeys.containsKey(sessionId)) {
|
if (room.inboundGroupSessions.containsKey(sessionId)) {
|
||||||
final roomKeyRequest =
|
final roomKeyRequest =
|
||||||
RoomKeyRequest.fromToDeviceEvent(toDeviceEvent, this);
|
RoomKeyRequest.fromToDeviceEvent(toDeviceEvent, this);
|
||||||
if (deviceKeys.userId == userID &&
|
if (deviceKeys.userId == userID &&
|
||||||
|
@ -1446,6 +1481,7 @@ class Client {
|
||||||
Future<void> _updateUserDeviceKeys() async {
|
Future<void> _updateUserDeviceKeys() async {
|
||||||
try {
|
try {
|
||||||
if (!isLogged()) return;
|
if (!isLogged()) return;
|
||||||
|
final dbActions = <Future<dynamic> Function()>[];
|
||||||
var trackedUserIds = await _getUserIdsInEncryptedRooms();
|
var trackedUserIds = await _getUserIdsInEncryptedRooms();
|
||||||
trackedUserIds.add(userID);
|
trackedUserIds.add(userID);
|
||||||
|
|
||||||
|
@ -1481,6 +1517,7 @@ class Client {
|
||||||
final String deviceId = rawDeviceKeyEntry.key;
|
final String deviceId = rawDeviceKeyEntry.key;
|
||||||
|
|
||||||
// Set the new device key for this device
|
// Set the new device key for this device
|
||||||
|
|
||||||
if (!oldKeys.containsKey(deviceId)) {
|
if (!oldKeys.containsKey(deviceId)) {
|
||||||
_userDeviceKeys[userId].deviceKeys[deviceId] =
|
_userDeviceKeys[userId].deviceKeys[deviceId] =
|
||||||
DeviceKeys.fromJson(rawDeviceKeyEntry.value);
|
DeviceKeys.fromJson(rawDeviceKeyEntry.value);
|
||||||
|
@ -1493,11 +1530,34 @@ class Client {
|
||||||
} else {
|
} else {
|
||||||
_userDeviceKeys[userId].deviceKeys[deviceId] = oldKeys[deviceId];
|
_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;
|
_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) {
|
rooms.forEach((Room room) {
|
||||||
if (room.encrypted) {
|
if (room.encrypted) {
|
||||||
room.clearOutboundGroupSession();
|
room.clearOutboundGroupSession();
|
||||||
|
@ -1616,7 +1676,7 @@ class Client {
|
||||||
oneTimeKeysCount) {
|
oneTimeKeysCount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await storeAPI?.storeClient();
|
await database?.updateClientKeys(pickledOlmAccount, id);
|
||||||
lastTimeKeysUploaded = DateTime.now();
|
lastTimeKeysUploaded = DateTime.now();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1668,7 +1728,7 @@ class Client {
|
||||||
var newSession = olm.Session();
|
var newSession = olm.Session();
|
||||||
newSession.create_inbound_from(_olmAccount, senderKey, body);
|
newSession.create_inbound_from(_olmAccount, senderKey, body);
|
||||||
_olmAccount.remove_one_time_keys(newSession);
|
_olmAccount.remove_one_time_keys(newSession);
|
||||||
storeAPI?.storeClient();
|
database?.updateClientKeys(pickledOlmAccount, id);
|
||||||
plaintext = newSession.decrypt(type, body);
|
plaintext = newSession.decrypt(type, body);
|
||||||
storeOlmSession(senderKey, newSession);
|
storeOlmSession(senderKey, newSession);
|
||||||
}
|
}
|
||||||
|
@ -1695,29 +1755,24 @@ class Client {
|
||||||
|
|
||||||
/// A map from Curve25519 identity keys to existing olm sessions.
|
/// A map from Curve25519 identity keys to existing olm sessions.
|
||||||
Map<String, List<olm.Session>> get olmSessions => _olmSessions;
|
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) {
|
void storeOlmSession(String curve25519IdentityKey, olm.Session session) {
|
||||||
if (!_olmSessions.containsKey(curve25519IdentityKey)) {
|
if (!_olmSessions.containsKey(curve25519IdentityKey)) {
|
||||||
_olmSessions[curve25519IdentityKey] = [];
|
_olmSessions[curve25519IdentityKey] = [];
|
||||||
}
|
}
|
||||||
if (_olmSessions[curve25519IdentityKey]
|
final ix = _olmSessions[curve25519IdentityKey]
|
||||||
.indexWhere((s) => s.session_id() == session.session_id()) ==
|
.indexWhere((s) => s.session_id() == session.session_id());
|
||||||
|
if (ix ==
|
||||||
-1) {
|
-1) {
|
||||||
|
// add a new session
|
||||||
_olmSessions[curve25519IdentityKey].add(session);
|
_olmSessions[curve25519IdentityKey].add(session);
|
||||||
|
} else {
|
||||||
|
// update an existing session
|
||||||
|
_olmSessions[curve25519IdentityKey][ix] = session;
|
||||||
}
|
}
|
||||||
var pickleMap = <String, List<String>>{};
|
final pickle = session.pickle(userID);
|
||||||
for (var entry in olmSessions.entries) {
|
database?.storeOlmSession(id, curve25519IdentityKey, session.session_id(), pickle);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send
|
/// 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 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||||
import './room.dart';
|
import './room.dart';
|
||||||
import 'utils/matrix_localizations.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.
|
/// 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 {
|
class Event {
|
||||||
|
@ -96,6 +97,8 @@ class Event {
|
||||||
|
|
||||||
User get stateKeyUser => room.getUserByMXIDSync(stateKey);
|
User get stateKeyUser => room.getUserByMXIDSync(stateKey);
|
||||||
|
|
||||||
|
double sortOrder;
|
||||||
|
|
||||||
Event(
|
Event(
|
||||||
{this.status = defaultStatus,
|
{this.status = defaultStatus,
|
||||||
this.content,
|
this.content,
|
||||||
|
@ -107,7 +110,8 @@ class Event {
|
||||||
this.unsigned,
|
this.unsigned,
|
||||||
this.prevContent,
|
this.prevContent,
|
||||||
this.stateKey,
|
this.stateKey,
|
||||||
this.room});
|
this.room,
|
||||||
|
this.sortOrder = 0.0});
|
||||||
|
|
||||||
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
||||||
if (payload is String) {
|
if (payload is String) {
|
||||||
|
@ -122,7 +126,7 @@ class Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a State event from a table row or from the event stream.
|
/// 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 content = Event.getMapFromPayload(jsonPayload['content']);
|
||||||
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
final unsigned = Event.getMapFromPayload(jsonPayload['unsigned']);
|
||||||
final prevContent = Event.getMapFromPayload(jsonPayload['prev_content']);
|
final prevContent = Event.getMapFromPayload(jsonPayload['prev_content']);
|
||||||
|
@ -140,6 +144,31 @@ class Event {
|
||||||
: DateTime.now(),
|
: DateTime.now(),
|
||||||
unsigned: unsigned,
|
unsigned: unsigned,
|
||||||
room: room,
|
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.
|
/// from the database and the timelines. Returns false if not removed.
|
||||||
Future<bool> remove() async {
|
Future<bool> remove() async {
|
||||||
if (status < 1) {
|
if (status < 1) {
|
||||||
if (room.client.store != null) {
|
await room.client.database?.removeEvent(room.client.id, eventId, room.id);
|
||||||
await room.client.store.removeEvent(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
room.client.onEvent.add(EventUpdate(
|
room.client.onEvent.add(EventUpdate(
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
|
@ -349,7 +376,8 @@ class Event {
|
||||||
'event_id': eventId,
|
'event_id': eventId,
|
||||||
'status': -2,
|
'status': -2,
|
||||||
'content': {'body': 'Removed...'}
|
'content': {'body': 'Removed...'}
|
||||||
}));
|
},
|
||||||
|
sortOrder: sortOrder));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -470,13 +498,13 @@ class Event {
|
||||||
// Is this file storeable?
|
// Is this file storeable?
|
||||||
final infoMap =
|
final infoMap =
|
||||||
getThumbnail ? content['info']['thumbnail_info'] : content['info'];
|
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 is Map<String, dynamic> &&
|
||||||
infoMap['size'] is int &&
|
infoMap['size'] is int &&
|
||||||
infoMap['size'] <= room.client.store.maxFileSize;
|
infoMap['size'] <= room.client.database.maxFileSize ;
|
||||||
|
|
||||||
if (storeable) {
|
if (storeable) {
|
||||||
uint8list = await room.client.store.getFile(mxContent.toString());
|
uint8list = await room.client.database.getFile(mxContent.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the file
|
// Download the file
|
||||||
|
@ -484,7 +512,7 @@ class Event {
|
||||||
uint8list =
|
uint8list =
|
||||||
(await http.get(mxContent.getDownloadLink(room.client))).bodyBytes;
|
(await http.get(mxContent.getDownloadLink(room.client))).bodyBytes;
|
||||||
if (storeable) {
|
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/>.
|
* 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 }
|
enum PresenceType { online, offline, unavailable }
|
||||||
|
|
||||||
/// Informs the client of a user's presence state change.
|
/// Informs the client of a user's presence state change.
|
||||||
|
@ -39,6 +42,8 @@ class Presence {
|
||||||
final String statusMsg;
|
final String statusMsg;
|
||||||
final DateTime time;
|
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)
|
Presence.fromJson(Map<String, dynamic> json)
|
||||||
: sender = json['sender'],
|
: sender = json['sender'],
|
||||||
displayname = json['content']['displayname'],
|
displayname = json['content']['displayname'],
|
||||||
|
@ -55,4 +60,23 @@ class Presence {
|
||||||
e.toString() == "PresenceType.${json['content']['presence']}",
|
e.toString() == "PresenceType.${json['content']['presence']}",
|
||||||
orElse: () => null),
|
orElse: () => null),
|
||||||
statusMsg = json['content']['status_msg'];
|
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/matrix_localizations.dart';
|
||||||
import 'utils/states_map.dart';
|
import 'utils/states_map.dart';
|
||||||
import './utils/markdown.dart';
|
import './utils/markdown.dart';
|
||||||
|
import './database/database.dart' show DbRoom;
|
||||||
|
|
||||||
enum PushRuleState { notify, mentions_only, dont_notify }
|
enum PushRuleState { notify, mentions_only, dont_notify }
|
||||||
enum JoinRules { public, knock, invite, private }
|
enum JoinRules { public, knock, invite, private }
|
||||||
|
@ -90,6 +91,27 @@ class Room {
|
||||||
|
|
||||||
List<String> _outboundGroupSessionDevices;
|
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
|
/// Clears the existing outboundGroupSession, tries to create a new one and
|
||||||
/// stores it as an ingoingGroupSession in the [sessionKeys]. Then sends the
|
/// stores it as an ingoingGroupSession in the [sessionKeys]. Then sends the
|
||||||
/// new session encrypted with olm to all non-blocked devices using
|
/// new session encrypted with olm to all non-blocked devices using
|
||||||
|
@ -120,7 +142,7 @@ class Room {
|
||||||
'session_id': outboundGroupSession.session_id(),
|
'session_id': outboundGroupSession.session_id(),
|
||||||
'session_key': outboundGroupSession.session_key(),
|
'session_key': outboundGroupSession.session_key(),
|
||||||
};
|
};
|
||||||
setSessionKey(rawSession['session_id'], rawSession);
|
setInboundGroupSession(rawSession['session_id'], rawSession);
|
||||||
try {
|
try {
|
||||||
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
|
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
|
||||||
_outboundGroupSession = outboundGroupSession;
|
_outboundGroupSession = outboundGroupSession;
|
||||||
|
@ -137,12 +159,10 @@ class Room {
|
||||||
|
|
||||||
Future<void> _storeOutboundGroupSession() async {
|
Future<void> _storeOutboundGroupSession() async {
|
||||||
if (_outboundGroupSession == null) return;
|
if (_outboundGroupSession == null) return;
|
||||||
await client.storeAPI?.setItem(
|
await client.database?.storeOutboundGroupSession(
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session',
|
client.id, id, _outboundGroupSession.pickle(client.userID),
|
||||||
_outboundGroupSession.pickle(client.userID));
|
json.encode(_outboundGroupSessionDevices),
|
||||||
await client.storeAPI?.setItem(
|
);
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices',
|
|
||||||
json.encode(_outboundGroupSessionDevices));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,12 +182,11 @@ class Room {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_outboundGroupSessionDevices == null;
|
if (!wipe && _outboundGroupSessionDevices == null && _outboundGroupSession == null) {
|
||||||
await client.storeAPI?.setItem(
|
return true; // let's just short-circuit out of here, no need to do DB stuff
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session', null);
|
}
|
||||||
await client.storeAPI?.setItem(
|
_outboundGroupSessionDevices = null;
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices',
|
await client.database?.removeOutboundGroupSession(client.id, id);
|
||||||
null);
|
|
||||||
_outboundGroupSession?.free();
|
_outboundGroupSession?.free();
|
||||||
_outboundGroupSession = null;
|
_outboundGroupSession = null;
|
||||||
return true;
|
return true;
|
||||||
|
@ -181,13 +200,13 @@ class Room {
|
||||||
/// "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
|
/// "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
|
||||||
/// "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
|
/// "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
|
||||||
/// }
|
/// }
|
||||||
Map<String, SessionKey> get sessionKeys => _sessionKeys;
|
Map<String, SessionKey> get inboundGroupSessions => _inboundGroupSessions;
|
||||||
Map<String, SessionKey> _sessionKeys = {};
|
Map<String, SessionKey> _inboundGroupSessions = {};
|
||||||
|
|
||||||
/// Add a new session key to the [sessionKeys].
|
/// 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}) {
|
{bool forwarded = false}) {
|
||||||
if (sessionKeys.containsKey(sessionId)) return;
|
if (inboundGroupSessions.containsKey(sessionId)) return;
|
||||||
olm.InboundGroupSession inboundGroupSession;
|
olm.InboundGroupSession inboundGroupSession;
|
||||||
if (content['algorithm'] == 'm.megolm.v1.aes-sha2') {
|
if (content['algorithm'] == 'm.megolm.v1.aes-sha2') {
|
||||||
try {
|
try {
|
||||||
|
@ -203,16 +222,17 @@ class Room {
|
||||||
e.toString());
|
e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_sessionKeys[sessionId] = SessionKey(
|
_inboundGroupSessions[sessionId] = SessionKey(
|
||||||
content: content,
|
content: content,
|
||||||
inboundGroupSession: inboundGroupSession,
|
inboundGroupSession: inboundGroupSession,
|
||||||
indexes: {},
|
indexes: {},
|
||||||
key: client.userID,
|
key: client.userID,
|
||||||
);
|
);
|
||||||
if (_fullyRestored) {
|
if (_fullyRestored) {
|
||||||
client.storeAPI?.setItem(
|
client.database?.storeInboundGroupSession(client.id, id, sessionId,
|
||||||
'/clients/${client.deviceID}/rooms/${id}/session_keys',
|
inboundGroupSession.pickle(client.userID), json.encode(content),
|
||||||
json.encode(sessionKeys));
|
json.encode({}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_tryAgainDecryptLastMessage();
|
_tryAgainDecryptLastMessage();
|
||||||
onSessionKeyReceived.add(sessionId);
|
onSessionKeyReceived.add(sessionId);
|
||||||
|
@ -395,7 +415,9 @@ class Room {
|
||||||
this.mInvitedMemberCount = 0,
|
this.mInvitedMemberCount = 0,
|
||||||
this.mJoinedMemberCount = 0,
|
this.mJoinedMemberCount = 0,
|
||||||
this.roomAccountData = const {},
|
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
|
/// The default count of how much events should be requested when requesting the
|
||||||
/// history of this room.
|
/// history of this room.
|
||||||
|
@ -746,20 +768,22 @@ class Room {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final sortOrder = newSortOrder;
|
||||||
// Display a *sending* event and store it.
|
// Display a *sending* event and store it.
|
||||||
var eventUpdate =
|
var eventUpdate = EventUpdate(type: 'timeline', roomID: id, eventType: type, sortOrder: sortOrder,
|
||||||
EventUpdate(type: 'timeline', roomID: id, eventType: type, content: {
|
content: {
|
||||||
'type': type,
|
'type': type,
|
||||||
'event_id': messageID,
|
'event_id': messageID,
|
||||||
'sender': client.userID,
|
'sender': client.userID,
|
||||||
'status': 0,
|
'status': 0,
|
||||||
'origin_server_ts': now,
|
'origin_server_ts': now,
|
||||||
'content': content
|
'content': content
|
||||||
});
|
},
|
||||||
|
);
|
||||||
client.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.database?.transaction(() async {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||||
return;
|
await updateSortOrder();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send the text and on success, store and display a *sent* event.
|
// Send the text and on success, store and display a *sent* event.
|
||||||
|
@ -767,7 +791,7 @@ class Room {
|
||||||
final response = await client.jsonRequest(
|
final response = await client.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: '/client/r0/rooms/${id}/send/$sendType/$messageID',
|
action: '/client/r0/rooms/${id}/send/$sendType/$messageID',
|
||||||
data: client.encryptionEnabled
|
data: encrypted && client.encryptionEnabled
|
||||||
? await encryptGroupMessagePayload(content)
|
? await encryptGroupMessagePayload(content)
|
||||||
: content);
|
: content);
|
||||||
final String res = response['event_id'];
|
final String res = response['event_id'];
|
||||||
|
@ -775,9 +799,8 @@ class Room {
|
||||||
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
||||||
eventUpdate.content['event_id'] = res;
|
eventUpdate.content['event_id'] = res;
|
||||||
client.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.database?.transaction(() async {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
|
@ -786,9 +809,8 @@ class Room {
|
||||||
eventUpdate.content['status'] = -1;
|
eventUpdate.content['status'] = -1;
|
||||||
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
eventUpdate.content['unsigned'] = {'transaction_id': messageID};
|
||||||
client.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
await client.store?.transaction(() {
|
await client.database?.transaction(() async {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
await client.database.storeEventUpdate(client.id, eventUpdate);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -809,7 +831,7 @@ class Room {
|
||||||
}
|
}
|
||||||
} on MatrixException catch (exception) {
|
} on MatrixException catch (exception) {
|
||||||
if (exception.errorMessage == 'No known servers') {
|
if (exception.errorMessage == 'No known servers') {
|
||||||
await client.store?.forgetRoom(id);
|
await client.database?.forgetRoom(client.id, id);
|
||||||
client.onRoomUpdate.add(
|
client.onRoomUpdate.add(
|
||||||
RoomUpdate(
|
RoomUpdate(
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -833,7 +855,7 @@ class Room {
|
||||||
|
|
||||||
/// Call the Matrix API to forget this room if you already left it.
|
/// Call the Matrix API to forget this room if you already left it.
|
||||||
Future<void> forget() async {
|
Future<void> forget() async {
|
||||||
await client.store?.forgetRoom(id);
|
await client.database?.forgetRoom(client.id, id);
|
||||||
await client.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST, action: '/client/r0/rooms/${id}/forget');
|
type: HTTPType.POST, action: '/client/r0/rooms/${id}/forget');
|
||||||
return;
|
return;
|
||||||
|
@ -903,65 +925,55 @@ class Room {
|
||||||
|
|
||||||
if (onHistoryReceived != null) onHistoryReceived();
|
if (onHistoryReceived != null) onHistoryReceived();
|
||||||
prev_batch = resp['end'];
|
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> &&
|
if (!(resp['chunk'] is List<dynamic> &&
|
||||||
resp['chunk'].length > 0 &&
|
resp['chunk'].length > 0 &&
|
||||||
resp['end'] is String)) return;
|
resp['end'] is String)) return;
|
||||||
|
|
||||||
if (resp['state'] is List<dynamic>) {
|
if (resp['state'] is List<dynamic>) {
|
||||||
await client.store?.transaction(() {
|
for (final state in resp['state']) {
|
||||||
for (var i = 0; i < resp['state'].length; i++) {
|
var eventUpdate = EventUpdate(
|
||||||
var eventUpdate = EventUpdate(
|
type: 'state',
|
||||||
type: 'state',
|
roomID: id,
|
||||||
roomID: id,
|
eventType: state['type'],
|
||||||
eventType: resp['state'][i]['type'],
|
content: state,
|
||||||
content: resp['state'][i],
|
sortOrder: oldSortOrder,
|
||||||
).decrypt(this);
|
).decrypt(this);
|
||||||
client.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
if (client.database != null) {
|
||||||
}
|
dbActions.add(() => client.database.storeEventUpdate(client.id, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<dynamic> history = resp['chunk'];
|
List<dynamic> history = resp['chunk'];
|
||||||
await client.store?.transaction(() {
|
for (final hist in history) {
|
||||||
for (var i = 0; i < history.length; i++) {
|
var eventUpdate = EventUpdate(
|
||||||
var eventUpdate = EventUpdate(
|
type: 'history',
|
||||||
type: 'history',
|
roomID: id,
|
||||||
roomID: id,
|
eventType: hist['type'],
|
||||||
eventType: history[i]['type'],
|
content: hist,
|
||||||
content: history[i],
|
sortOrder: oldSortOrder,
|
||||||
).decrypt(this);
|
).decrypt(this);
|
||||||
client.onEvent.add(eventUpdate);
|
client.onEvent.add(eventUpdate);
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
if (client.database != null) {
|
||||||
client.store.setRoomPrevBatch(id, resp['end']);
|
dbActions.add(() => client.database.storeEventUpdate(client.id, eventUpdate));
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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(
|
client.onRoomUpdate.add(
|
||||||
RoomUpdate(
|
RoomUpdate(
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -1013,7 +1025,7 @@ class Room {
|
||||||
/// Sends *m.fully_read* and *m.read* for the given event ID.
|
/// Sends *m.fully_read* and *m.read* for the given event ID.
|
||||||
Future<void> sendReadReceipt(String eventID) async {
|
Future<void> sendReadReceipt(String eventID) async {
|
||||||
notificationCount = 0;
|
notificationCount = 0;
|
||||||
await client?.store?.resetNotificationCount(id);
|
await client.database?.resetNotificationCount(client.id, id);
|
||||||
await client.jsonRequest(
|
await client.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: '/client/r0/rooms/$id/read_markers',
|
action: '/client/r0/rooms/$id/read_markers',
|
||||||
|
@ -1024,48 +1036,44 @@ class Room {
|
||||||
return;
|
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
|
// Restore the inbound and outbound session keys
|
||||||
if (client.encryptionEnabled && client.storeAPI != null) {
|
if (client.encryptionEnabled && client.database != null) {
|
||||||
final String outboundGroupSessionPickle = await client.storeAPI.getItem(
|
outboundGroupSession ??= client.database.getDbOutboundGroupSession(client.id, id);
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session');
|
inboundGroupSessions ??= client.database.getDbInboundGroupSessions(client.id, id);
|
||||||
if (outboundGroupSessionPickle != null) {
|
if (outboundGroupSession is Future) {
|
||||||
|
outboundGroupSession = await outboundGroupSession;
|
||||||
|
}
|
||||||
|
if (inboundGroupSessions is Future) {
|
||||||
|
inboundGroupSessions = await inboundGroupSessions;
|
||||||
|
}
|
||||||
|
if (outboundGroupSession != false && outboundGroupSession != null) {
|
||||||
try {
|
try {
|
||||||
_outboundGroupSession = olm.OutboundGroupSession();
|
_outboundGroupSession = olm.OutboundGroupSession();
|
||||||
_outboundGroupSession.unpickle(
|
_outboundGroupSession.unpickle(
|
||||||
client.userID, outboundGroupSessionPickle);
|
client.userID, outboundGroupSession.pickle);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_outboundGroupSession = null;
|
_outboundGroupSession = null;
|
||||||
print('[LibOlm] Unable to unpickle outboundGroupSession: ' +
|
print('[LibOlm] Unable to unpickle outboundGroupSession: ' +
|
||||||
e.toString());
|
e.toString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
final String outboundGroupSessionDevicesString = await client.storeAPI
|
|
||||||
.getItem(
|
|
||||||
'/clients/${client.deviceID}/rooms/${id}/outbound_group_session_devices');
|
|
||||||
if (outboundGroupSessionDevicesString != null) {
|
|
||||||
_outboundGroupSessionDevices =
|
_outboundGroupSessionDevices =
|
||||||
List<String>.from(json.decode(outboundGroupSessionDevicesString));
|
List<String>.from(json.decode(outboundGroupSession.deviceIds));
|
||||||
}
|
}
|
||||||
final String sessionKeysPickle = await client.storeAPI
|
if (inboundGroupSessions?.isNotEmpty ?? false) {
|
||||||
.getItem('/clients/${client.deviceID}/rooms/${id}/session_keys');
|
_inboundGroupSessions ??= {};
|
||||||
if (sessionKeysPickle?.isNotEmpty ?? false) {
|
for (final sessionKey in inboundGroupSessions) {
|
||||||
final Map<String, dynamic> map = json.decode(sessionKeysPickle);
|
|
||||||
_sessionKeys ??= {};
|
|
||||||
for (var entry in map.entries) {
|
|
||||||
try {
|
try {
|
||||||
_sessionKeys[entry.key] =
|
_inboundGroupSessions[sessionKey.sessionId] = SessionKey.fromDb(sessionKey, client.userID);
|
||||||
SessionKey.fromJson(entry.value, client.userID);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[LibOlm] Could not unpickle inboundGroupSession: ' +
|
print('[LibOlm] Could not unpickle inboundGroupSession: ' + e.toString());
|
||||||
e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await client.storeAPI?.setItem(
|
|
||||||
'/clients/${client.deviceID}/rooms/${id}/session_keys',
|
|
||||||
json.encode(sessionKeys));
|
|
||||||
_tryAgainDecryptLastMessage();
|
_tryAgainDecryptLastMessage();
|
||||||
_fullyRestored = true;
|
_fullyRestored = true;
|
||||||
return;
|
return;
|
||||||
|
@ -1076,44 +1084,64 @@ class Room {
|
||||||
/// Returns a Room from a json String which comes normally from the store. If the
|
/// Returns a Room from a json String which comes normally from the store. If the
|
||||||
/// state are also given, the method will await them.
|
/// state are also given, the method will await them.
|
||||||
static Future<Room> getRoomFromTableRow(
|
static Future<Room> getRoomFromTableRow(
|
||||||
Map<String, dynamic> row, Client matrix,
|
DbRoom row, // either Map<String, dynamic> or DbRoom
|
||||||
{Future<List<Map<String, dynamic>>> states,
|
Client matrix,
|
||||||
Future<List<Map<String, dynamic>>> roomAccountData}) async {
|
{
|
||||||
var newRoom = Room(
|
dynamic states, // DbRoomState, as iterator and optionally as future
|
||||||
id: row['room_id'],
|
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
|
membership: Membership.values
|
||||||
.firstWhere((e) => e.toString() == 'Membership.' + row['membership']),
|
.firstWhere((e) => e.toString() == 'Membership.' + row.membership),
|
||||||
notificationCount: row['notification_count'],
|
notificationCount: row.notificationCount,
|
||||||
highlightCount: row['highlight_count'],
|
highlightCount: row.highlightCount,
|
||||||
notificationSettings: row['notification_settings'],
|
notificationSettings: 'mention', // TODO: do proper things
|
||||||
prev_batch: row['prev_batch'],
|
prev_batch: row.prevBatch,
|
||||||
mInvitedMemberCount: row['invited_member_count'],
|
mInvitedMemberCount: row.invitedMemberCount,
|
||||||
mJoinedMemberCount: row['joined_member_count'],
|
mJoinedMemberCount: row.joinedMemberCount,
|
||||||
mHeroes: row['heroes']?.split(',') ?? [],
|
mHeroes: row.heroes?.split(',') ?? [],
|
||||||
client: matrix,
|
client: matrix,
|
||||||
roomAccountData: {},
|
roomAccountData: {},
|
||||||
|
newestSortOrder: row.newestSortOrder,
|
||||||
|
oldestSortOrder: row.oldestSortOrder,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore the inbound and outbound session keys
|
|
||||||
await newRoom.restoreGroupSessionKeys();
|
|
||||||
|
|
||||||
if (states != null) {
|
if (states != null) {
|
||||||
var rawStates = await states;
|
var rawStates;
|
||||||
for (var i = 0; i < rawStates.length; i++) {
|
if (states is Future) {
|
||||||
var newState = Event.fromJson(rawStates[i], newRoom);
|
rawStates = await states;
|
||||||
|
} else {
|
||||||
|
rawStates = states;
|
||||||
|
}
|
||||||
|
for (final rawState in rawStates) {
|
||||||
|
final newState = Event.fromDb(rawState, newRoom);;
|
||||||
newRoom.setState(newState);
|
newRoom.setState(newState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRoomAccountData = <String, RoomAccountData>{};
|
var newRoomAccountData = <String, RoomAccountData>{};
|
||||||
if (roomAccountData != null) {
|
if (roomAccountData != null) {
|
||||||
var rawRoomAccountData = await roomAccountData;
|
var rawRoomAccountData;
|
||||||
for (var i = 0; i < rawRoomAccountData.length; i++) {
|
if (roomAccountData is Future) {
|
||||||
var newData = RoomAccountData.fromJson(rawRoomAccountData[i], newRoom);
|
rawRoomAccountData = await roomAccountData;
|
||||||
|
} else {
|
||||||
|
rawRoomAccountData = roomAccountData;
|
||||||
|
}
|
||||||
|
for (final singleAccountData in rawRoomAccountData) {
|
||||||
|
final newData = RoomAccountData.fromDb(singleAccountData, newRoom);
|
||||||
newRoomAccountData[newData.typeKey] = newData;
|
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;
|
return newRoom;
|
||||||
}
|
}
|
||||||
|
@ -1122,24 +1150,28 @@ class Room {
|
||||||
Future<Timeline> getTimeline(
|
Future<Timeline> getTimeline(
|
||||||
{onTimelineUpdateCallback onUpdate,
|
{onTimelineUpdateCallback onUpdate,
|
||||||
onTimelineInsertCallback onInsert}) async {
|
onTimelineInsertCallback onInsert}) async {
|
||||||
var events = client.store != null
|
var events;
|
||||||
? await client.store.getEventList(this)
|
if (client.database != null) {
|
||||||
: <Event>[];
|
events = await client.database.getEventList(client.id, this);
|
||||||
|
} else {
|
||||||
|
events = <Event>[];
|
||||||
|
}
|
||||||
|
|
||||||
// Try again to decrypt encrypted events and update the database.
|
// Try again to decrypt encrypted events and update the database.
|
||||||
if (encrypted && client.store != null) {
|
if (encrypted && client.database != null) {
|
||||||
await client.store.transaction(() {
|
await client.database.transaction(() async {
|
||||||
for (var i = 0; i < events.length; i++) {
|
for (var i = 0; i < events.length; i++) {
|
||||||
if (events[i].type == EventTypes.Encrypted &&
|
if (events[i].type == EventTypes.Encrypted &&
|
||||||
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) {
|
events[i].content['body'] == DecryptError.UNKNOWN_SESSION) {
|
||||||
events[i] = events[i].decrypted;
|
events[i] = events[i].decrypted;
|
||||||
if (events[i].type != EventTypes.Encrypted) {
|
if (events[i].type != EventTypes.Encrypted) {
|
||||||
client.store.storeEventUpdate(
|
await client.database.storeEventUpdate(client.id,
|
||||||
EventUpdate(
|
EventUpdate(
|
||||||
eventType: events[i].typeKey,
|
eventType: events[i].typeKey,
|
||||||
content: events[i].toJson(),
|
content: events[i].toJson(),
|
||||||
roomID: events[i].roomId,
|
roomID: events[i].roomId,
|
||||||
type: 'timeline',
|
type: 'timeline',
|
||||||
|
sortOrder: events[i].sortOrder,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1154,7 +1186,7 @@ class Room {
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
onInsert: onInsert,
|
onInsert: onInsert,
|
||||||
);
|
);
|
||||||
if (client.store == null) {
|
if (client.database == null) {
|
||||||
prev_batch = '';
|
prev_batch = '';
|
||||||
await requestHistory(historyCount: 10);
|
await requestHistory(historyCount: 10);
|
||||||
}
|
}
|
||||||
|
@ -1227,6 +1259,9 @@ class Room {
|
||||||
/// Requests a missing [User] for this room. Important for clients using
|
/// Requests a missing [User] for this room. Important for clients using
|
||||||
/// lazy loading.
|
/// lazy loading.
|
||||||
Future<User> requestUser(String mxID, {bool ignoreErrors = false}) async {
|
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;
|
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
||||||
Map<String, dynamic> resp;
|
Map<String, dynamic> resp;
|
||||||
try {
|
try {
|
||||||
|
@ -1237,23 +1272,30 @@ class Room {
|
||||||
_requestingMatrixIds.remove(mxID);
|
_requestingMatrixIds.remove(mxID);
|
||||||
if (!ignoreErrors) rethrow;
|
if (!ignoreErrors) rethrow;
|
||||||
}
|
}
|
||||||
|
if (resp == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final user = User(mxID,
|
final user = User(mxID,
|
||||||
displayName: resp['displayname'],
|
displayName: resp['displayname'],
|
||||||
avatarUrl: resp['avatar_url'],
|
avatarUrl: resp['avatar_url'],
|
||||||
room: this);
|
room: this);
|
||||||
states[mxID] = user;
|
states[mxID] = user;
|
||||||
if (client.store != null) {
|
await client.database?.transaction(() async {
|
||||||
await client.store.transaction(() {
|
final content = <String, dynamic>{
|
||||||
client.store.storeEventUpdate(
|
'sender': mxID,
|
||||||
EventUpdate(
|
'type': 'm.room.member',
|
||||||
content: resp,
|
'content': resp,
|
||||||
roomID: id,
|
'state_key': mxID,
|
||||||
type: 'state',
|
};
|
||||||
eventType: 'm.room.member'),
|
await client.database.storeEventUpdate(client.id,
|
||||||
);
|
EventUpdate(
|
||||||
return;
|
content: content,
|
||||||
});
|
roomID: id,
|
||||||
}
|
type: 'state',
|
||||||
|
eventType: 'm.room.member',
|
||||||
|
sortOrder: 0.0),
|
||||||
|
);
|
||||||
|
});
|
||||||
if (onUpdate != null) onUpdate.add(id);
|
if (onUpdate != null) onUpdate.add(id);
|
||||||
_requestingMatrixIds.remove(mxID);
|
_requestingMatrixIds.remove(mxID);
|
||||||
return user;
|
return user;
|
||||||
|
@ -1690,12 +1732,11 @@ class Room {
|
||||||
Future<List<DeviceKeys>> getUserDeviceKeys() async {
|
Future<List<DeviceKeys>> getUserDeviceKeys() async {
|
||||||
var deviceKeys = <DeviceKeys>[];
|
var deviceKeys = <DeviceKeys>[];
|
||||||
var users = await requestParticipants();
|
var users = await requestParticipants();
|
||||||
for (final userDeviceKeyEntry in client.userDeviceKeys.entries) {
|
for (final user in users) {
|
||||||
if (users.indexWhere((u) => u.id == userDeviceKeyEntry.key) == -1) {
|
if (client.userDeviceKeys.containsKey(user.id)) {
|
||||||
continue;
|
for (var deviceKeyEntry in client.userDeviceKeys[user.id].deviceKeys.values) {
|
||||||
}
|
deviceKeys.add(deviceKeyEntry);
|
||||||
for (var deviceKeyEntry in userDeviceKeyEntry.value.deviceKeys.values) {
|
}
|
||||||
deviceKeys.add(deviceKeyEntry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return deviceKeys;
|
return deviceKeys;
|
||||||
|
@ -1745,23 +1786,23 @@ class Room {
|
||||||
throw (DecryptError.UNKNOWN_ALGORITHM);
|
throw (DecryptError.UNKNOWN_ALGORITHM);
|
||||||
}
|
}
|
||||||
final String sessionId = event.content['session_id'];
|
final String sessionId = event.content['session_id'];
|
||||||
if (!sessionKeys.containsKey(sessionId)) {
|
if (!inboundGroupSessions.containsKey(sessionId)) {
|
||||||
throw (DecryptError.UNKNOWN_SESSION);
|
throw (DecryptError.UNKNOWN_SESSION);
|
||||||
}
|
}
|
||||||
final decryptResult = sessionKeys[sessionId]
|
final decryptResult = inboundGroupSessions[sessionId]
|
||||||
.inboundGroupSession
|
.inboundGroupSession
|
||||||
.decrypt(event.content['ciphertext']);
|
.decrypt(event.content['ciphertext']);
|
||||||
final messageIndexKey =
|
final messageIndexKey =
|
||||||
event.eventId + event.time.millisecondsSinceEpoch.toString();
|
event.eventId + event.time.millisecondsSinceEpoch.toString();
|
||||||
if (sessionKeys[sessionId].indexes.containsKey(messageIndexKey) &&
|
if (inboundGroupSessions[sessionId].indexes.containsKey(messageIndexKey) &&
|
||||||
sessionKeys[sessionId].indexes[messageIndexKey] !=
|
inboundGroupSessions[sessionId].indexes[messageIndexKey] !=
|
||||||
decryptResult.message_index) {
|
decryptResult.message_index) {
|
||||||
if ((_outboundGroupSession?.session_id() ?? '') == sessionId) {
|
if ((_outboundGroupSession?.session_id() ?? '') == sessionId) {
|
||||||
clearOutboundGroupSession();
|
clearOutboundGroupSession();
|
||||||
}
|
}
|
||||||
throw (DecryptError.CHANNEL_CORRUPTED);
|
throw (DecryptError.CHANNEL_CORRUPTED);
|
||||||
}
|
}
|
||||||
sessionKeys[sessionId].indexes[messageIndexKey] =
|
inboundGroupSessions[sessionId].indexes[messageIndexKey] =
|
||||||
decryptResult.message_index;
|
decryptResult.message_index;
|
||||||
_storeOutboundGroupSession();
|
_storeOutboundGroupSession();
|
||||||
decryptedPayload = json.decode(decryptResult.plaintext);
|
decryptedPayload = json.decode(decryptResult.plaintext);
|
||||||
|
@ -1799,6 +1840,7 @@ class Room {
|
||||||
stateKey: event.stateKey,
|
stateKey: event.stateKey,
|
||||||
prevContent: event.prevContent,
|
prevContent: event.prevContent,
|
||||||
status: event.status,
|
status: event.status,
|
||||||
|
sortOrder: event.sortOrder,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/account_data.dart';
|
import 'package:famedlysdk/src/account_data.dart';
|
||||||
import 'package:famedlysdk/src/event.dart';
|
import 'package:famedlysdk/src/event.dart';
|
||||||
|
import './database/database.dart' show DbRoomAccountData;
|
||||||
|
|
||||||
/// Stripped down events for account data and ephemrals of a room.
|
/// Stripped down events for account data and ephemrals of a room.
|
||||||
class RoomAccountData extends AccountData {
|
class RoomAccountData extends AccountData {
|
||||||
|
@ -46,4 +47,14 @@ class RoomAccountData extends AccountData {
|
||||||
roomId: jsonPayload['room_id'],
|
roomId: jsonPayload['room_id'],
|
||||||
room: room);
|
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.
|
// The json payload of the content of this event.
|
||||||
final Map<String, dynamic> content;
|
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) {
|
EventUpdate decrypt(Room room) {
|
||||||
if (eventType != 'm.room.encrypted') {
|
if (eventType != 'm.room.encrypted') {
|
||||||
|
@ -48,12 +51,13 @@ class EventUpdate {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var decrpytedEvent =
|
var decrpytedEvent =
|
||||||
room.decryptGroupMessage(Event.fromJson(content, room));
|
room.decryptGroupMessage(Event.fromJson(content, room, sortOrder));
|
||||||
return EventUpdate(
|
return EventUpdate(
|
||||||
eventType: eventType,
|
eventType: eventType,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
type: type,
|
type: type,
|
||||||
content: decrpytedEvent.toJson(),
|
content: decrpytedEvent.toJson(),
|
||||||
|
sortOrder: sortOrder,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
|
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
|
||||||
|
|
|
@ -122,7 +122,7 @@ class Timeline {
|
||||||
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
||||||
if (eventId != null) {
|
if (eventId != null) {
|
||||||
events[eventId]
|
events[eventId]
|
||||||
.setRedactionEvent(Event.fromJson(eventUpdate.content, room));
|
.setRedactionEvent(Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder));
|
||||||
}
|
}
|
||||||
} else if (eventUpdate.content['status'] == -2) {
|
} else if (eventUpdate.content['status'] == -2) {
|
||||||
var i = _findEvent(event_id: eventUpdate.content['event_id']);
|
var i = _findEvent(event_id: eventUpdate.content['event_id']);
|
||||||
|
@ -138,18 +138,18 @@ class Timeline {
|
||||||
: null);
|
: null);
|
||||||
|
|
||||||
if (i < events.length) {
|
if (i < events.length) {
|
||||||
events[i] = Event.fromJson(eventUpdate.content, room);
|
events[i] = Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Event newEvent;
|
Event newEvent;
|
||||||
var senderUser = await room.client.store
|
var senderUser = room.getState('m.room.member', eventUpdate.content['sender'])?.asUser ?? await room.client.database
|
||||||
?.getUser(matrixID: eventUpdate.content['sender'], room: room);
|
?.getUser(room.client.id, eventUpdate.content['sender'], room);
|
||||||
if (senderUser != null) {
|
if (senderUser != null) {
|
||||||
eventUpdate.content['displayname'] = senderUser.displayName;
|
eventUpdate.content['displayname'] = senderUser.displayName;
|
||||||
eventUpdate.content['avatar_url'] = senderUser.avatarUrl.toString();
|
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' &&
|
if (eventUpdate.type == 'history' &&
|
||||||
events.indexWhere(
|
events.indexWhere(
|
||||||
|
@ -173,8 +173,7 @@ class Timeline {
|
||||||
void sort() {
|
void sort() {
|
||||||
if (sortLock || events.length < 2) return;
|
if (sortLock || events.length < 2) return;
|
||||||
sortLock = true;
|
sortLock = true;
|
||||||
events?.sort((a, b) =>
|
events?.sort((a, b) => b.sortOrder - a.sortOrder > 0 ? 1 : -1);
|
||||||
b.time.millisecondsSinceEpoch.compareTo(a.time.millisecondsSinceEpoch));
|
|
||||||
sortLock = false;
|
sortLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import '../client.dart';
|
import '../client.dart';
|
||||||
|
import '../database/database.dart' show DbUserDeviceKey, DbUserDeviceKeysKey;
|
||||||
|
import '../event.dart';
|
||||||
|
|
||||||
class DeviceKeysList {
|
class DeviceKeysList {
|
||||||
String userId;
|
String userId;
|
||||||
bool outdated = true;
|
bool outdated = true;
|
||||||
Map<String, DeviceKeys> deviceKeys = {};
|
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) {
|
DeviceKeysList.fromJson(Map<String, dynamic> json) {
|
||||||
userId = json['user_id'];
|
userId = json['user_id'];
|
||||||
outdated = json['outdated'];
|
outdated = json['outdated'];
|
||||||
|
@ -52,7 +63,7 @@ class DeviceKeys {
|
||||||
|
|
||||||
Future<void> setVerified(bool newVerified, Client client) {
|
Future<void> setVerified(bool newVerified, Client client) {
|
||||||
verified = newVerified;
|
verified = newVerified;
|
||||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
return client.database?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setBlocked(bool newBlocked, Client client) {
|
Future<void> setBlocked(bool newBlocked, Client client) {
|
||||||
|
@ -63,7 +74,7 @@ class DeviceKeys {
|
||||||
room.clearOutboundGroupSession();
|
room.clearOutboundGroupSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return client.storeAPI.storeUserDeviceKeys(client.userDeviceKeys);
|
return client.database?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceKeys({
|
DeviceKeys({
|
||||||
|
@ -77,6 +88,22 @@ class DeviceKeys {
|
||||||
this.blocked,
|
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) {
|
DeviceKeys.fromJson(Map<String, dynamic> json) {
|
||||||
userId = json['user_id'];
|
userId = json['user_id'];
|
||||||
deviceId = json['device_id'];
|
deviceId = json['device_id'];
|
||||||
|
|
|
@ -16,7 +16,7 @@ class RoomKeyRequest extends ToDeviceEvent {
|
||||||
|
|
||||||
Future<void> forwardKey() async {
|
Future<void> forwardKey() async {
|
||||||
var room = this.room;
|
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];
|
var forwardedKeys = <dynamic>[client.identityKey];
|
||||||
for (final key in session.forwardingCurve25519KeyChain) {
|
for (final key in session.forwardingCurve25519KeyChain) {
|
||||||
forwardedKeys.add(key);
|
forwardedKeys.add(key);
|
||||||
|
|
|
@ -2,6 +2,9 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:olm/olm.dart';
|
import 'package:olm/olm.dart';
|
||||||
|
|
||||||
|
import '../database/database.dart' show DbInboundGroupSession;
|
||||||
|
import '../event.dart';
|
||||||
|
|
||||||
class SessionKey {
|
class SessionKey {
|
||||||
Map<String, dynamic> content;
|
Map<String, dynamic> content;
|
||||||
Map<String, int> indexes;
|
Map<String, int> indexes;
|
||||||
|
@ -14,6 +17,20 @@ class SessionKey {
|
||||||
|
|
||||||
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
|
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 {
|
SessionKey.fromJson(Map<String, dynamic> json, String key) : key = key {
|
||||||
content = json['content'] != null
|
content = json['content'] != null
|
||||||
? Map<String, dynamic>.from(json['content'])
|
? Map<String, dynamic>.from(json['content'])
|
||||||
|
|
119
pubspec.lock
119
pubspec.lock
|
@ -1,13 +1,27 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
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:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -42,42 +56,42 @@ packages:
|
||||||
name: build
|
name: build
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.4"
|
version: "1.2.2"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_config
|
name: build_config
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.2"
|
||||||
build_daemon:
|
build_daemon:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "2.1.4"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.3.7"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.2"
|
version: "1.9.0"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "5.1.0"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -91,7 +105,7 @@ packages:
|
||||||
name: built_value
|
name: built_value
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.6.0"
|
version: "7.1.0"
|
||||||
canonical_json:
|
canonical_json:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -106,13 +120,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
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:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -154,7 +182,7 @@ packages:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.7"
|
version: "1.3.6"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -169,13 +197,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.9"
|
version: "0.10.9"
|
||||||
front_end:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: front_end
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.1.18"
|
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -252,14 +273,7 @@ packages:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "3.0.1"
|
||||||
kernel:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: kernel
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.18"
|
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -311,6 +325,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
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:
|
multi_server_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -396,7 +431,7 @@ packages:
|
||||||
name: pubspec_parse
|
name: pubspec_parse
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4"
|
version: "0.1.5"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -404,6 +439,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.5"
|
||||||
|
recase:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: recase
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -432,6 +474,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.3"
|
version: "0.2.3"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.5"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -453,6 +502,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.5"
|
version: "1.5.5"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -473,7 +529,7 @@ packages:
|
||||||
name: stream_transform
|
name: stream_transform
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.19"
|
version: "1.2.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -481,6 +537,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -515,7 +578,7 @@ packages:
|
||||||
name: timing
|
name: timing
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1+1"
|
version: "0.1.1+2"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -14,6 +14,7 @@ dependencies:
|
||||||
image: ^2.1.4
|
image: ^2.1.4
|
||||||
markdown: ^2.1.3
|
markdown: ^2.1.3
|
||||||
html_unescape: ^1.0.1+3
|
html_unescape: ^1.0.1+3
|
||||||
|
moor: ^3.0.2
|
||||||
|
|
||||||
olm:
|
olm:
|
||||||
git:
|
git:
|
||||||
|
@ -27,5 +28,7 @@ dependencies:
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.0.0
|
test: ^1.0.0
|
||||||
|
moor_generator: ^3.0.0
|
||||||
build_runner: ^1.5.2
|
build_runner: ^1.5.2
|
||||||
pedantic: ^1.9.0 # DO NOT UPDATE AS THIS WOULD CAUSE FLUTTER TO FAIL
|
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 'package:test/test.dart';
|
||||||
|
|
||||||
import 'fake_matrix_api.dart';
|
import 'fake_matrix_api.dart';
|
||||||
import 'fake_store.dart';
|
import 'fake_database.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Client matrix;
|
Client matrix;
|
||||||
|
@ -86,7 +86,6 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(matrix.homeserver, null);
|
expect(matrix.homeserver, null);
|
||||||
expect(matrix.matrixVersions, null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await matrix.checkServer('https://fakeserver.wrongaddress');
|
await matrix.checkServer('https://fakeserver.wrongaddress');
|
||||||
|
@ -95,8 +94,6 @@ void main() {
|
||||||
}
|
}
|
||||||
await matrix.checkServer('https://fakeserver.notexisting');
|
await matrix.checkServer('https://fakeserver.notexisting');
|
||||||
expect(matrix.homeserver, '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
|
final resp = await matrix
|
||||||
.jsonRequest(type: HTTPType.POST, action: '/client/r0/login', data: {
|
.jsonRequest(type: HTTPType.POST, action: '/client/r0/login', data: {
|
||||||
|
@ -128,7 +125,6 @@ void main() {
|
||||||
newHomeserver: matrix.homeserver,
|
newHomeserver: matrix.homeserver,
|
||||||
newDeviceName: 'Text Matrix Client',
|
newDeviceName: 'Text Matrix Client',
|
||||||
newDeviceID: resp['device_id'],
|
newDeviceID: resp['device_id'],
|
||||||
newMatrixVersions: matrix.matrixVersions,
|
|
||||||
newOlmAccount: pickledOlmAccount,
|
newOlmAccount: pickledOlmAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -160,18 +156,18 @@ void main() {
|
||||||
expect(matrix.directChats, matrix.accountData['m.direct'].content);
|
expect(matrix.directChats, matrix.accountData['m.direct'].content);
|
||||||
expect(matrix.presences.length, 1);
|
expect(matrix.presences.length, 1);
|
||||||
expect(matrix.rooms[1].ephemerals.length, 2);
|
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||||
expect(matrix.rooms[1].sessionKeys.length, 1);
|
expect(matrix.rooms[1].inboundGroupSessions.length, 1);
|
||||||
expect(
|
expect(
|
||||||
matrix
|
matrix
|
||||||
.rooms[1]
|
.rooms[1]
|
||||||
.sessionKeys['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
.inboundGroupSessions['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||||
.content['session_key'],
|
.content['session_key'],
|
||||||
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw');
|
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw');
|
||||||
if (olmEnabled) {
|
if (olmEnabled) {
|
||||||
expect(
|
expect(
|
||||||
matrix
|
matrix
|
||||||
.rooms[1]
|
.rooms[1]
|
||||||
.sessionKeys['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
.inboundGroupSessions['ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||||
.inboundGroupSession !=
|
.inboundGroupSession !=
|
||||||
null,
|
null,
|
||||||
true);
|
true);
|
||||||
|
@ -279,7 +275,6 @@ void main() {
|
||||||
expect(matrix.userID == null, true);
|
expect(matrix.userID == null, true);
|
||||||
expect(matrix.deviceID == null, true);
|
expect(matrix.deviceID == null, true);
|
||||||
expect(matrix.deviceName == null, true);
|
expect(matrix.deviceName == null, true);
|
||||||
expect(matrix.matrixVersions == null, true);
|
|
||||||
expect(matrix.prevBatch == null, true);
|
expect(matrix.prevBatch == null, true);
|
||||||
|
|
||||||
var loginState = await loginStateFuture;
|
var loginState = await loginStateFuture;
|
||||||
|
@ -635,8 +630,7 @@ void main() {
|
||||||
test('Test the fake store api', () async {
|
test('Test the fake store api', () async {
|
||||||
var client1 = Client('testclient', debug: true);
|
var client1 = Client('testclient', debug: true);
|
||||||
client1.httpClient = FakeMatrixApi();
|
client1.httpClient = FakeMatrixApi();
|
||||||
var fakeStore = FakeStore(client1, {});
|
client1.database = getDatabase();
|
||||||
client1.storeAPI = fakeStore;
|
|
||||||
|
|
||||||
client1.connect(
|
client1.connect(
|
||||||
newToken: 'abc123',
|
newToken: 'abc123',
|
||||||
|
@ -644,14 +638,6 @@ void main() {
|
||||||
newHomeserver: 'https://fakeServer.notExisting',
|
newHomeserver: 'https://fakeServer.notExisting',
|
||||||
newDeviceName: 'Text Matrix Client',
|
newDeviceName: 'Text Matrix Client',
|
||||||
newDeviceID: 'GHTYAJCE',
|
newDeviceID: 'GHTYAJCE',
|
||||||
newMatrixVersions: [
|
|
||||||
'r0.0.1',
|
|
||||||
'r0.1.0',
|
|
||||||
'r0.2.0',
|
|
||||||
'r0.3.0',
|
|
||||||
'r0.4.0',
|
|
||||||
'r0.5.0'
|
|
||||||
],
|
|
||||||
newOlmAccount: pickledOlmAccount,
|
newOlmAccount: pickledOlmAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -669,8 +655,9 @@ void main() {
|
||||||
|
|
||||||
var client2 = Client('testclient', debug: true);
|
var client2 = Client('testclient', debug: true);
|
||||||
client2.httpClient = FakeMatrixApi();
|
client2.httpClient = FakeMatrixApi();
|
||||||
client2.storeAPI = FakeStore(client2, fakeStore.storeMap);
|
client2.database = client1.database;
|
||||||
|
|
||||||
|
client2.connect();
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
|
||||||
expect(client2.isLogged(), true);
|
expect(client2.isLogged(), true);
|
||||||
|
@ -679,11 +666,10 @@ void main() {
|
||||||
expect(client2.homeserver, client1.homeserver);
|
expect(client2.homeserver, client1.homeserver);
|
||||||
expect(client2.deviceID, client1.deviceID);
|
expect(client2.deviceID, client1.deviceID);
|
||||||
expect(client2.deviceName, client1.deviceName);
|
expect(client2.deviceName, client1.deviceName);
|
||||||
expect(client2.matrixVersions, client1.matrixVersions);
|
|
||||||
if (client2.encryptionEnabled) {
|
if (client2.encryptionEnabled) {
|
||||||
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
|
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
|
||||||
expect(json.encode(client2.rooms[1].sessionKeys[sessionKey]),
|
expect(json.encode(client2.rooms[1].inboundGroupSessions[sessionKey]),
|
||||||
json.encode(client1.rooms[1].sessionKeys[sessionKey]));
|
json.encode(client1.rooms[1].inboundGroupSessions[sessionKey]));
|
||||||
expect(client2.rooms[1].id, client1.rooms[1].id);
|
expect(client2.rooms[1].id, client1.rooms[1].id);
|
||||||
expect(client2.rooms[1].outboundGroupSession.session_key(), sessionKey);
|
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 'package:test/test.dart';
|
||||||
|
|
||||||
import 'fake_matrix_api.dart';
|
import 'fake_matrix_api.dart';
|
||||||
import 'fake_store.dart';
|
import 'fake_database.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
/// All Tests related to device keys
|
/// All Tests related to device keys
|
||||||
|
@ -53,13 +53,13 @@ void main() {
|
||||||
|
|
||||||
var matrix = Client('testclient', debug: true);
|
var matrix = Client('testclient', debug: true);
|
||||||
matrix.httpClient = FakeMatrixApi();
|
matrix.httpClient = FakeMatrixApi();
|
||||||
matrix.storeAPI = FakeStore(matrix, {});
|
matrix.database = getDatabase();
|
||||||
await matrix.checkServer('https://fakeServer.notExisting');
|
await matrix.checkServer('https://fakeServer.notExisting');
|
||||||
await matrix.login('test', '1234');
|
await matrix.login('test', '1234');
|
||||||
var room = matrix.getRoomById('!726s6s6q:example.com');
|
var room = matrix.getRoomById('!726s6s6q:example.com');
|
||||||
if (matrix.encryptionEnabled) {
|
if (matrix.encryptionEnabled) {
|
||||||
await room.createOutboundGroupSession();
|
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(
|
var roomKeyRequest = RoomKeyRequest.fromToDeviceEvent(
|
||||||
ToDeviceEvent.fromJson(rawJson), matrix);
|
ToDeviceEvent.fromJson(rawJson), matrix);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import 'package:famedlysdk/src/event.dart';
|
||||||
import 'package:famedlysdk/src/room.dart';
|
import 'package:famedlysdk/src/room.dart';
|
||||||
import 'package:famedlysdk/src/user.dart';
|
import 'package:famedlysdk/src/user.dart';
|
||||||
import 'package:famedlysdk/src/utils/matrix_file.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 'package:test/test.dart';
|
||||||
|
|
||||||
import 'fake_matrix_api.dart';
|
import 'fake_matrix_api.dart';
|
||||||
|
@ -62,44 +63,50 @@ void main() {
|
||||||
'@charley:example.org'
|
'@charley:example.org'
|
||||||
];
|
];
|
||||||
|
|
||||||
var jsonObj = <String, dynamic>{
|
var dbRoom = DbRoom(
|
||||||
'room_id': id,
|
clientId: 1,
|
||||||
'membership': membership.toString().split('.').last,
|
roomId: id,
|
||||||
'avatar_url': '',
|
membership: membership.toString().split('.').last,
|
||||||
'notification_count': notificationCount,
|
highlightCount: highlightCount,
|
||||||
'highlight_count': highlightCount,
|
notificationCount: notificationCount,
|
||||||
'prev_batch': '',
|
prevBatch: '',
|
||||||
'joined_member_count': notificationCount,
|
joinedMemberCount: notificationCount,
|
||||||
'invited_member_count': notificationCount,
|
invitedMemberCount: notificationCount,
|
||||||
'heroes': heroes.join(','),
|
newestSortOrder: 0.0,
|
||||||
};
|
oldestSortOrder: 0.0,
|
||||||
|
heroes: heroes.join(','),
|
||||||
|
);
|
||||||
|
|
||||||
Function states = () async => [
|
var states = [
|
||||||
{
|
DbRoomState(
|
||||||
'content': {'join_rule': 'public'},
|
clientId: 1,
|
||||||
'event_id': '143273582443PhrSn:example.org',
|
eventId: '143273582443PhrSn:example.org',
|
||||||
'origin_server_ts': 1432735824653,
|
roomId: id,
|
||||||
'room_id': id,
|
sortOrder: 0.0,
|
||||||
'sender': '@example:example.org',
|
originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653),
|
||||||
'state_key': '',
|
sender: '@example:example.org',
|
||||||
'type': 'm.room.join_rules',
|
type: 'm.room.join_rules',
|
||||||
'unsigned': {'age': 1234}
|
unsigned: '{"age": 1234}',
|
||||||
}
|
content: '{"join_rule": "public"}',
|
||||||
];
|
prevContent: '',
|
||||||
|
stateKey: '',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
Function roomAccountData = () async => [
|
var roomAccountData = [
|
||||||
{
|
DbRoomAccountData(
|
||||||
'content': {'foo': 'bar'},
|
clientId: 1,
|
||||||
'room_id': id,
|
type: 'com.test.foo',
|
||||||
'type': 'com.test.foo'
|
roomId: id,
|
||||||
}
|
content: '{"foo": "bar"}',
|
||||||
];
|
),
|
||||||
|
];
|
||||||
|
|
||||||
room = await Room.getRoomFromTableRow(
|
room = await Room.getRoomFromTableRow(
|
||||||
jsonObj,
|
dbRoom,
|
||||||
matrix,
|
matrix,
|
||||||
states: states(),
|
states: states,
|
||||||
roomAccountData: roomAccountData(),
|
roomAccountData: roomAccountData,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(room.id, id);
|
expect(room.id, id);
|
||||||
|
@ -390,14 +397,14 @@ void main() {
|
||||||
expect(room.outboundGroupSession != null, true);
|
expect(room.outboundGroupSession != null, true);
|
||||||
expect(room.outboundGroupSession.session_id().isNotEmpty, true);
|
expect(room.outboundGroupSession.session_id().isNotEmpty, true);
|
||||||
expect(
|
expect(
|
||||||
room.sessionKeys.containsKey(room.outboundGroupSession.session_id()),
|
room.inboundGroupSessions.containsKey(room.outboundGroupSession.session_id()),
|
||||||
true);
|
true);
|
||||||
expect(
|
expect(
|
||||||
room.sessionKeys[room.outboundGroupSession.session_id()]
|
room.inboundGroupSessions[room.outboundGroupSession.session_id()]
|
||||||
.content['session_key'],
|
.content['session_key'],
|
||||||
room.outboundGroupSession.session_key());
|
room.outboundGroupSession.session_key());
|
||||||
expect(
|
expect(
|
||||||
room.sessionKeys[room.outboundGroupSession.session_id()].indexes
|
room.inboundGroupSessions[room.outboundGroupSession.session_id()].indexes
|
||||||
.length,
|
.length,
|
||||||
0);
|
0);
|
||||||
});
|
});
|
||||||
|
|
|
@ -65,7 +65,8 @@ void main() {
|
||||||
'status': 2,
|
'status': 2,
|
||||||
'event_id': '1',
|
'event_id': '1',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
}));
|
},
|
||||||
|
sortOrder: room.newSortOrder));
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: 'timeline',
|
||||||
|
@ -78,7 +79,8 @@ void main() {
|
||||||
'status': 2,
|
'status': 2,
|
||||||
'event_id': '2',
|
'event_id': '2',
|
||||||
'origin_server_ts': testTimeStamp - 1000
|
'origin_server_ts': testTimeStamp - 1000
|
||||||
}));
|
},
|
||||||
|
sortOrder: room.oldSortOrder));
|
||||||
|
|
||||||
expect(timeline.sub != null, true);
|
expect(timeline.sub != null, true);
|
||||||
|
|
||||||
|
@ -125,7 +127,8 @@ void main() {
|
||||||
'redacts': '2',
|
'redacts': '2',
|
||||||
'event_id': '3',
|
'event_id': '3',
|
||||||
'origin_server_ts': testTimeStamp + 1000
|
'origin_server_ts': testTimeStamp + 1000
|
||||||
}));
|
},
|
||||||
|
sortOrder: room.newSortOrder));
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
@ -159,7 +162,8 @@ void main() {
|
||||||
'event_id': '42',
|
'event_id': '42',
|
||||||
'unsigned': {'transaction_id': '1234'},
|
'unsigned': {'transaction_id': '1234'},
|
||||||
'origin_server_ts': DateTime.now().millisecondsSinceEpoch
|
'origin_server_ts': DateTime.now().millisecondsSinceEpoch
|
||||||
}));
|
},
|
||||||
|
sortOrder: room.newSortOrder));
|
||||||
|
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
@ -182,7 +186,8 @@ void main() {
|
||||||
'status': 0,
|
'status': 0,
|
||||||
'event_id': 'abc',
|
'event_id': 'abc',
|
||||||
'origin_server_ts': testTimeStamp
|
'origin_server_ts': testTimeStamp
|
||||||
}));
|
},
|
||||||
|
sortOrder: room.newSortOrder));
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
await room.sendTextEvent('test', txid: 'errortxid');
|
await room.sendTextEvent('test', txid: 'errortxid');
|
||||||
await Future.delayed(Duration(milliseconds: 50));
|
await Future.delayed(Duration(milliseconds: 50));
|
||||||
|
@ -230,9 +235,9 @@ void main() {
|
||||||
|
|
||||||
expect(updateCount, 20);
|
expect(updateCount, 20);
|
||||||
expect(timeline.events.length, 9);
|
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[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');
|
expect(room.prev_batch, 't47409-4357353_219380_26003_2265');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import '../test/fake_store.dart';
|
import '../test/fake_database.dart';
|
||||||
|
|
||||||
void main() => test();
|
void main() => test();
|
||||||
|
|
||||||
|
@ -18,14 +18,14 @@ const String testMessage6 = 'Hello mars';
|
||||||
void test() async {
|
void test() async {
|
||||||
print('++++ Login $testUserA ++++');
|
print('++++ Login $testUserA ++++');
|
||||||
var testClientA = Client('TestClient', debug: false);
|
var testClientA = Client('TestClient', debug: false);
|
||||||
testClientA.storeAPI = FakeStore(testClientA, <String, dynamic>{});
|
testClientA.database = getDatabase();
|
||||||
await testClientA.checkServer(homeserver);
|
await testClientA.checkServer(homeserver);
|
||||||
await testClientA.login(testUserA, testPasswordA);
|
await testClientA.login(testUserA, testPasswordA);
|
||||||
assert(testClientA.encryptionEnabled);
|
assert(testClientA.encryptionEnabled);
|
||||||
|
|
||||||
print('++++ Login $testUserB ++++');
|
print('++++ Login $testUserB ++++');
|
||||||
var testClientB = Client('TestClient', debug: false);
|
var testClientB = Client('TestClient', debug: false);
|
||||||
testClientB.storeAPI = FakeStore(testClientB, <String, dynamic>{});
|
testClientB.database = getDatabase();
|
||||||
await testClientB.checkServer(homeserver);
|
await testClientB.checkServer(homeserver);
|
||||||
await testClientB.login(testUserB, testPasswordA);
|
await testClientB.login(testUserB, testPasswordA);
|
||||||
assert(testClientB.encryptionEnabled);
|
assert(testClientB.encryptionEnabled);
|
||||||
|
@ -128,12 +128,12 @@ void test() async {
|
||||||
await Future.delayed(Duration(seconds: 5));
|
await Future.delayed(Duration(seconds: 5));
|
||||||
assert(room.outboundGroupSession != null);
|
assert(room.outboundGroupSession != null);
|
||||||
var currentSessionIdA = room.outboundGroupSession.session_id();
|
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(testClientA.olmSessions[testClientB.identityKey].length == 1);
|
||||||
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
||||||
assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() ==
|
assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() ==
|
||||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(room.outboundGroupSession.session_id()));
|
.containsKey(room.outboundGroupSession.session_id()));
|
||||||
assert(room.lastMessage == testMessage);
|
assert(room.lastMessage == testMessage);
|
||||||
assert(inviteRoom.lastMessage == testMessage);
|
assert(inviteRoom.lastMessage == testMessage);
|
||||||
|
@ -149,7 +149,7 @@ void test() async {
|
||||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||||
|
|
||||||
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(room.outboundGroupSession.session_id()));
|
.containsKey(room.outboundGroupSession.session_id()));
|
||||||
assert(room.lastMessage == testMessage2);
|
assert(room.lastMessage == testMessage2);
|
||||||
assert(inviteRoom.lastMessage == testMessage2);
|
assert(inviteRoom.lastMessage == testMessage2);
|
||||||
|
@ -163,9 +163,9 @@ void test() async {
|
||||||
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
assert(testClientB.olmSessions[testClientA.identityKey].length == 1);
|
||||||
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
assert(room.outboundGroupSession.session_id() == currentSessionIdA);
|
||||||
assert(inviteRoom.outboundGroupSession != null);
|
assert(inviteRoom.outboundGroupSession != null);
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
||||||
assert(room.sessionKeys
|
assert(room.inboundGroupSessions
|
||||||
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
.containsKey(inviteRoom.outboundGroupSession.session_id()));
|
||||||
assert(inviteRoom.lastMessage == testMessage3);
|
assert(inviteRoom.lastMessage == testMessage3);
|
||||||
assert(room.lastMessage == testMessage3);
|
assert(room.lastMessage == testMessage3);
|
||||||
|
@ -174,7 +174,7 @@ void test() async {
|
||||||
|
|
||||||
print('++++ Login $testUserB in another client ++++');
|
print('++++ Login $testUserB in another client ++++');
|
||||||
var testClientC = Client('TestClient', debug: false);
|
var testClientC = Client('TestClient', debug: false);
|
||||||
testClientC.storeAPI = FakeStore(testClientC, <String, dynamic>{});
|
testClientC.database = getDatabase();
|
||||||
await testClientC.checkServer(homeserver);
|
await testClientC.checkServer(homeserver);
|
||||||
await testClientC.login(testUserB, testPasswordA);
|
await testClientC.login(testUserB, testPasswordA);
|
||||||
await Future.delayed(Duration(seconds: 3));
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
@ -193,7 +193,7 @@ void test() async {
|
||||||
testClientC.olmSessions[testClientA.identityKey].first.session_id());
|
testClientC.olmSessions[testClientA.identityKey].first.session_id());
|
||||||
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
||||||
currentSessionIdA = room.outboundGroupSession.session_id();
|
currentSessionIdA = room.outboundGroupSession.session_id();
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(room.outboundGroupSession.session_id()));
|
.containsKey(room.outboundGroupSession.session_id()));
|
||||||
assert(room.lastMessage == testMessage4);
|
assert(room.lastMessage == testMessage4);
|
||||||
assert(inviteRoom.lastMessage == testMessage4);
|
assert(inviteRoom.lastMessage == testMessage4);
|
||||||
|
@ -216,7 +216,7 @@ void test() async {
|
||||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||||
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
assert(room.outboundGroupSession.session_id() != currentSessionIdA);
|
||||||
currentSessionIdA = room.outboundGroupSession.session_id();
|
currentSessionIdA = room.outboundGroupSession.session_id();
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(room.outboundGroupSession.session_id()));
|
.containsKey(room.outboundGroupSession.session_id()));
|
||||||
assert(room.lastMessage == testMessage6);
|
assert(room.lastMessage == testMessage6);
|
||||||
assert(inviteRoom.lastMessage == testMessage6);
|
assert(inviteRoom.lastMessage == testMessage6);
|
||||||
|
@ -224,21 +224,22 @@ void test() async {
|
||||||
"++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++");
|
"++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++");
|
||||||
|
|
||||||
print('++++ ($testUserA) Restore user ++++');
|
print('++++ ($testUserA) Restore user ++++');
|
||||||
FakeStore clientAStore = testClientA.storeAPI;
|
final clientADatabase = testClientA.database;
|
||||||
testClientA = null;
|
testClientA = null;
|
||||||
testClientA = Client('TestClient', debug: false);
|
testClientA = Client('TestClient', debug: false);
|
||||||
testClientA.storeAPI = FakeStore(testClientA, clientAStore.storeMap);
|
testClientA.database = clientADatabase;
|
||||||
|
testClientA.connect();
|
||||||
await Future.delayed(Duration(seconds: 3));
|
await Future.delayed(Duration(seconds: 3));
|
||||||
var restoredRoom = testClientA.rooms.first;
|
var restoredRoom = testClientA.rooms.first;
|
||||||
assert(room != null);
|
assert(room != null);
|
||||||
assert(restoredRoom.id == room.id);
|
assert(restoredRoom.id == room.id);
|
||||||
assert(restoredRoom.outboundGroupSession.session_id() ==
|
assert(restoredRoom.outboundGroupSession.session_id() ==
|
||||||
room.outboundGroupSession.session_id());
|
room.outboundGroupSession.session_id());
|
||||||
assert(restoredRoom.sessionKeys.length == 4);
|
assert(restoredRoom.inboundGroupSessions.length == 4);
|
||||||
assert(restoredRoom.sessionKeys.length == room.sessionKeys.length);
|
assert(restoredRoom.inboundGroupSessions.length == room.inboundGroupSessions.length);
|
||||||
for (var i = 0; i < restoredRoom.sessionKeys.length; i++) {
|
for (var i = 0; i < restoredRoom.inboundGroupSessions.length; i++) {
|
||||||
assert(restoredRoom.sessionKeys.keys.toList()[i] ==
|
assert(restoredRoom.inboundGroupSessions.keys.toList()[i] ==
|
||||||
room.sessionKeys.keys.toList()[i]);
|
room.inboundGroupSessions.keys.toList()[i]);
|
||||||
}
|
}
|
||||||
assert(testClientA.olmSessions[testClientB.identityKey].length == 1);
|
assert(testClientA.olmSessions[testClientB.identityKey].length == 1);
|
||||||
assert(testClientB.olmSessions[testClientA.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() ==
|
assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() ==
|
||||||
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
testClientB.olmSessions[testClientA.identityKey].first.session_id());
|
||||||
/*assert(restoredRoom.outboundGroupSession.session_id() == currentSessionIdA);
|
/*assert(restoredRoom.outboundGroupSession.session_id() == currentSessionIdA);
|
||||||
assert(inviteRoom.sessionKeys
|
assert(inviteRoom.inboundGroupSessions
|
||||||
.containsKey(restoredRoom.outboundGroupSession.session_id()));*/
|
.containsKey(restoredRoom.outboundGroupSession.session_id()));*/
|
||||||
assert(restoredRoom.lastMessage == testMessage5);
|
assert(restoredRoom.lastMessage == testMessage5);
|
||||||
assert(inviteRoom.lastMessage == testMessage5);
|
assert(inviteRoom.lastMessage == testMessage5);
|
||||||
|
|
Loading…
Reference in a new issue