Merge branch 'main' into voip/add-voip-function
This commit is contained in:
commit
7efe123274
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -1,3 +1,40 @@
|
|||
## [0.7.0-nullsafety.10] - 26nd Nov 2021
|
||||
- feat: Migrate olm sessions on database migration
|
||||
- chore: Enable E2EE recovery by default
|
||||
|
||||
## [0.7.0-nullsafety.9] - 25nd Nov 2021
|
||||
- fix: Limited timeline clean up on web
|
||||
- fix: Remove account avatar
|
||||
|
||||
## [0.7.0-nullsafety.8] - 24nd Nov 2021
|
||||
- chore: Update FluffyBox
|
||||
|
||||
## [0.7.0-nullsafety.7] - 23nd Nov 2021
|
||||
- feat: Add commands to create chats
|
||||
- feat: Add clear cache command
|
||||
- feat: Implement new FluffyBox database API implementation
|
||||
- fix: Workaround for a null exception for a non nullable boolean while user device key updating
|
||||
- fix: Limited timeline clears too many events
|
||||
- fix: Ability to remove avatar from room and account
|
||||
- fix: Request history in archived rooms
|
||||
- fix: Decrypt last event of a room
|
||||
- refactor: Remove Sembast database implementation
|
||||
|
||||
## [0.7.0-nullsafety.6] - 16nd Nov 2021
|
||||
- feat: Implement sembast store
|
||||
- fix: HtmlToText crashes with an empty code block
|
||||
- fix: use originServerTs to check if state event is old
|
||||
- fix: Dont enable e2ee in new direct chats without encryption support
|
||||
- fix: Change eventstatus of edits in prevEvent
|
||||
- chore: Trim formatted username fallback
|
||||
|
||||
## [0.7.0-nullsafety.5] - 10nd Nov 2021
|
||||
- fix: Edits as lastEvent do not update
|
||||
- fix: JSON parsing in decryptRoomEvent method
|
||||
- fix: Wrong null check in hive database
|
||||
- fix: crash on invalid displaynames
|
||||
- chore: Update matrix_api_lite
|
||||
|
||||
## [0.7.0-nullsafety.4] - 09nd Nov 2021
|
||||
- feat: More advanced create chat methods (encryption is now enabled by default)
|
||||
- feat: Make waiting on init db optional
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import 'utils/bootstrap.dart';
|
|||
class Encryption {
|
||||
final Client client;
|
||||
final bool debug;
|
||||
final bool enableE2eeRecovery;
|
||||
|
||||
bool get enabled => olmManager.enabled;
|
||||
|
||||
|
|
@ -53,7 +52,6 @@ class Encryption {
|
|||
Encryption({
|
||||
required this.client,
|
||||
this.debug = false,
|
||||
required this.enableE2eeRecovery,
|
||||
}) {
|
||||
ssss = SSSS(this);
|
||||
keyManager = KeyManager(this);
|
||||
|
|
@ -232,8 +230,7 @@ class Encryption {
|
|||
decryptedPayload = json.decode(decryptResult.plaintext);
|
||||
} catch (exception) {
|
||||
// alright, if this was actually by our own outbound group session, we might as well clear it
|
||||
if (client.enableE2eeRecovery &&
|
||||
exception.toString() != DecryptException.unknownSession &&
|
||||
if (exception.toString() != DecryptException.unknownSession &&
|
||||
(keyManager
|
||||
.getOutboundGroupSession(roomId)
|
||||
?.outboundGroupSession
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ class KeyManager {
|
|||
// attempt to decrypt the last event
|
||||
final event = room.getState(EventTypes.Encrypted);
|
||||
if (event != null && event.content['session_id'] == sessionId) {
|
||||
encryption.decryptRoomEvent(roomId, event, store: true);
|
||||
room.setState(encryption.decryptRoomEventSync(roomId, event));
|
||||
}
|
||||
// and finally broadcast the new session
|
||||
room.onSessionKeyReceived.add(sessionId);
|
||||
|
|
@ -219,8 +219,7 @@ class KeyManager {
|
|||
void maybeAutoRequest(String roomId, String sessionId, String senderKey) {
|
||||
final room = client.getRoomById(roomId);
|
||||
final requestIdent = '$roomId|$sessionId|$senderKey';
|
||||
if (client.enableE2eeRecovery &&
|
||||
room != null &&
|
||||
if (room != null &&
|
||||
!_requestedSessionIds.contains(requestIdent) &&
|
||||
!client.isUnknownSession) {
|
||||
// do e2ee recovery
|
||||
|
|
|
|||
|
|
@ -514,10 +514,10 @@ class OlmManager {
|
|||
return _decryptToDeviceEvent(event);
|
||||
} catch (_) {
|
||||
// okay, the thing errored while decrypting. It is safe to assume that the olm session is corrupt and we should generate a new one
|
||||
if (client.enableE2eeRecovery) {
|
||||
// ignore: unawaited_futures
|
||||
runInRoot(() => restoreOlmSession(event.senderId, senderKey));
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
runInRoot(() => restoreOlmSession(event.senderId, senderKey));
|
||||
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export 'package:matrix_api_lite/matrix_api_lite.dart';
|
|||
export 'src/client.dart';
|
||||
export 'src/database/database_api.dart';
|
||||
export 'src/database/hive_database.dart';
|
||||
export 'src/database/fluffybox_database.dart';
|
||||
export 'src/event.dart';
|
||||
export 'src/event_status.dart';
|
||||
export 'src/voip.dart';
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ class Client extends MatrixApi {
|
|||
|
||||
DatabaseApi? get database => _database;
|
||||
|
||||
bool enableE2eeRecovery;
|
||||
|
||||
@deprecated
|
||||
MatrixApi get api => this;
|
||||
|
||||
|
|
@ -120,7 +118,6 @@ class Client extends MatrixApi {
|
|||
/// [databaseBuilder]: A function that creates the database instance, that will be used.
|
||||
/// [legacyDatabaseBuilder]: Use this for your old database implementation to perform an automatic migration
|
||||
/// [databaseDestroyer]: A function that can be used to destroy a database instance, for example by deleting files from disk.
|
||||
/// [enableE2eeRecovery]: Enable additional logic to try to recover from bad e2ee sessions
|
||||
/// [verificationMethods]: A set of all the verification methods this client can handle. Includes:
|
||||
/// KeyVerificationMethod.numbers: Compare numbers. Most basic, should be supported
|
||||
/// KeyVerificationMethod.emoji: Compare emojis
|
||||
|
|
@ -157,7 +154,8 @@ class Client extends MatrixApi {
|
|||
this.databaseDestroyer,
|
||||
this.legacyDatabaseBuilder,
|
||||
this.legacyDatabaseDestroyer,
|
||||
this.enableE2eeRecovery = false,
|
||||
@Deprecated('This is now always enabled by default.')
|
||||
bool? enableE2eeRecovery,
|
||||
Set<KeyVerificationMethod>? verificationMethods,
|
||||
http.Client? httpClient,
|
||||
Set<String>? importantStateEvents,
|
||||
|
|
@ -568,7 +566,8 @@ class Client extends MatrixApi {
|
|||
final directChatRoomId = getDirectChatFromUserId(mxid);
|
||||
if (directChatRoomId != null) return directChatRoomId;
|
||||
|
||||
enableEncryption ??= await userOwnsEncryptionKeys(mxid);
|
||||
enableEncryption ??=
|
||||
encryptionEnabled && await userOwnsEncryptionKeys(mxid);
|
||||
if (enableEncryption) {
|
||||
initialState ??= [];
|
||||
if (!initialState.any((s) => s.type == EventTypes.Encryption)) {
|
||||
|
|
@ -609,6 +608,7 @@ class Client extends MatrixApi {
|
|||
List<String>? invite,
|
||||
CreateRoomPreset preset = CreateRoomPreset.privateChat,
|
||||
List<StateEvent>? initialState,
|
||||
Visibility? visibility,
|
||||
bool waitForSync = true,
|
||||
}) async {
|
||||
enableEncryption ??=
|
||||
|
|
@ -629,6 +629,7 @@ class Client extends MatrixApi {
|
|||
preset: preset,
|
||||
name: groupName,
|
||||
initialState: initialState,
|
||||
visibility: visibility,
|
||||
);
|
||||
|
||||
if (waitForSync) {
|
||||
|
|
@ -649,7 +650,7 @@ class Client extends MatrixApi {
|
|||
return true;
|
||||
}
|
||||
final keys = await queryKeys({userId: []});
|
||||
return keys.deviceKeys?.isNotEmpty ?? false;
|
||||
return keys.deviceKeys?[userId]?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
/// Creates a new space and returns the Room ID. The parameters are mostly
|
||||
|
|
@ -772,6 +773,7 @@ class Client extends MatrixApi {
|
|||
leftRoom,
|
||||
));
|
||||
});
|
||||
leftRoom.prev_batch = room.timeline?.prevBatch;
|
||||
room.state?.forEach((event) {
|
||||
leftRoom.setState(Event.fromMatrixEvent(
|
||||
event,
|
||||
|
|
@ -817,8 +819,15 @@ class Client extends MatrixApi {
|
|||
}
|
||||
}
|
||||
|
||||
/// Uploads a new user avatar for this user.
|
||||
Future<void> setAvatar(MatrixFile file) async {
|
||||
/// Uploads a new user avatar for this user. Leave file null to remove the
|
||||
/// current avatar.
|
||||
Future<void> setAvatar(MatrixFile? file) async {
|
||||
if (file == null) {
|
||||
// We send an empty String to remove the avatar. Sending Null **should**
|
||||
// work but it doesn't with Synapse. See:
|
||||
// https://gitlab.com/famedly/company/frontend/famedlysdk/-/issues/254
|
||||
return setAvatarUrl(userID!, Uri.parse(''));
|
||||
}
|
||||
final uploadResp = await uploadContent(
|
||||
file.bytes,
|
||||
filename: file.name,
|
||||
|
|
@ -1063,8 +1072,7 @@ class Client extends MatrixApi {
|
|||
// make sure to throw an exception if libolm doesn't exist
|
||||
await olm.init();
|
||||
olm.get_library_version();
|
||||
encryption =
|
||||
Encryption(client: this, enableE2eeRecovery: enableE2eeRecovery);
|
||||
encryption = Encryption(client: this);
|
||||
} catch (_) {
|
||||
encryption?.dispose();
|
||||
encryption = null;
|
||||
|
|
@ -1806,7 +1814,13 @@ class Client extends MatrixApi {
|
|||
final deviceKeysList =
|
||||
_userDeviceKeys[userId] ??= DeviceKeysList(userId, this);
|
||||
final failure = _keyQueryFailures[userId.domain];
|
||||
if (deviceKeysList.outdated &&
|
||||
|
||||
// deviceKeysList.outdated is not nullable but we have seen this error
|
||||
// in production: `Failed assertion: boolean expression must not be null`
|
||||
// So this could either be a null safety bug in Dart or a result of
|
||||
// using unsound null safety. The extra equal check `!= false` should
|
||||
// save us here.
|
||||
if (deviceKeysList.outdated != false &&
|
||||
(failure == null ||
|
||||
DateTime.now()
|
||||
.subtract(Duration(minutes: 5))
|
||||
|
|
@ -2381,6 +2395,24 @@ class Client extends MatrixApi {
|
|||
);
|
||||
}
|
||||
}
|
||||
Logs().d('Migrate OLM sessions...');
|
||||
try {
|
||||
final olmSessions = await legacyDatabase.getAllOlmSessions();
|
||||
for (final identityKey in olmSessions.keys) {
|
||||
final sessions = olmSessions[identityKey]!;
|
||||
for (final sessionId in sessions.keys) {
|
||||
final session = sessions[sessionId]!;
|
||||
await database.storeOlmSession(
|
||||
identityKey,
|
||||
session['session_id'] as String,
|
||||
session['pickle'] as String,
|
||||
session['last_received'] as int,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logs().e('Unable to migrate OLM sessions!', e, s);
|
||||
}
|
||||
Logs().d('Migrate Device Keys...');
|
||||
final userDeviceKeys = await legacyDatabase.getUserDeviceKeys(this);
|
||||
for (final userId in userDeviceKeys.keys) {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ abstract class DatabaseApi {
|
|||
|
||||
Future<Event?> getEventById(String eventId, Room room);
|
||||
|
||||
bool eventIsKnown(String eventId, String roomId);
|
||||
|
||||
Future<void> forgetRoom(String roomId);
|
||||
|
||||
Future<void> clearCache();
|
||||
|
|
@ -272,6 +270,8 @@ abstract class DatabaseApi {
|
|||
String userId,
|
||||
);
|
||||
|
||||
Future<Map<String, Map>> getAllOlmSessions();
|
||||
|
||||
Future<List<OlmSession>> getOlmSessionsForDevices(
|
||||
List<String> identityKeys,
|
||||
String userId,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -371,10 +371,6 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
|||
return Event.fromJson(convertToJson(raw), room);
|
||||
}
|
||||
|
||||
@override
|
||||
bool eventIsKnown(String eventId, String roomId) =>
|
||||
_eventsBox.keys.contains(MultiKey(roomId, eventId).toString());
|
||||
|
||||
/// Loads a whole list of events at once from the store for a specific room
|
||||
Future<List<Event>> _getEventsByIds(List<String> eventIds, Room room) =>
|
||||
Future.wait(eventIds
|
||||
|
|
@ -489,6 +485,21 @@ class FamedlySdkHiveDatabase extends DatabaseApi {
|
|||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, Map>> getAllOlmSessions() async {
|
||||
final backup = Map.fromEntries(
|
||||
await Future.wait(
|
||||
_olmSessionsBox.keys.map(
|
||||
(key) async => MapEntry(
|
||||
key,
|
||||
await _olmSessionsBox.get(key),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return backup.cast<String, Map>();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<OlmSession>> getOlmSessionsForDevices(
|
||||
List<String> identityKey, String userId) async {
|
||||
|
|
|
|||
|
|
@ -187,7 +187,8 @@ class Room {
|
|||
state.relationshipEventId != null &&
|
||||
state.relationshipType == RelationshipTypes.edit &&
|
||||
lastEvent != null &&
|
||||
!lastEvent.matchesEventOrTransactionId(state.relationshipEventId)) {
|
||||
!state.matchesEventOrTransactionId(lastEvent.eventId) &&
|
||||
lastEvent.eventId != state.relationshipEventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +203,8 @@ class Room {
|
|||
final prevEvent = getState(state.type, stateKey);
|
||||
if (prevEvent != null &&
|
||||
prevEvent.eventId != state.eventId &&
|
||||
client.database?.eventIsKnown(state.eventId, roomId) == true) {
|
||||
prevEvent.originServerTs.millisecondsSinceEpoch >
|
||||
state.originServerTs.millisecondsSinceEpoch) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1354,15 +1356,18 @@ class Room {
|
|||
}
|
||||
|
||||
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
||||
/// m.room.avatar event.
|
||||
Future<String> setAvatar(MatrixFile file) async {
|
||||
final uploadResp =
|
||||
await client.uploadContent(file.bytes, filename: file.name);
|
||||
/// m.room.avatar event. Leave empty to remove the current avatar.
|
||||
Future<String> setAvatar(MatrixFile? file) async {
|
||||
final uploadResp = file == null
|
||||
? null
|
||||
: await client.uploadContent(file.bytes, filename: file.name);
|
||||
return await client.setRoomStateWithKey(
|
||||
id,
|
||||
EventTypes.RoomAvatar,
|
||||
'',
|
||||
{'url': uploadResp.toString()},
|
||||
{
|
||||
if (uploadResp != null) 'url': uploadResp.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,13 +120,12 @@ class Timeline {
|
|||
this.onRemove,
|
||||
}) : events = events ?? [] {
|
||||
sub = room.client.onEvent.stream.listen(_handleEventUpdate);
|
||||
|
||||
// If the timeline is limited we want to clear our events cache
|
||||
roomSub = room.client.onSync.stream
|
||||
.where((sync) => sync.rooms?.join?[room.id]?.timeline?.limited == true)
|
||||
.listen((_) {
|
||||
this.events.clear();
|
||||
aggregatedEvents.clear();
|
||||
});
|
||||
.listen(_removeEventsNotInThisSync);
|
||||
|
||||
sessionIdReceivedSub =
|
||||
room.onSessionKeyReceived.stream.listen(_sessionKeyReceived);
|
||||
|
||||
|
|
@ -136,6 +135,13 @@ class Timeline {
|
|||
}
|
||||
}
|
||||
|
||||
/// Removes all entries from [events] which are not in this SyncUpdate.
|
||||
void _removeEventsNotInThisSync(SyncUpdate sync) {
|
||||
final newSyncEvents = sync.rooms?.join?[room.id]?.timeline?.events ?? [];
|
||||
final keepEventIds = newSyncEvents.map((e) => e.eventId);
|
||||
events.removeWhere((e) => !keepEventIds.contains(e.eventId));
|
||||
}
|
||||
|
||||
/// Don't forget to call this before you dismiss this object!
|
||||
void cancelSubscriptions() {
|
||||
sub?.cancel();
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class User extends Event {
|
|||
words[i] = words[i][0].toUpperCase() + words[i].substring(1);
|
||||
}
|
||||
}
|
||||
return words.join(' ');
|
||||
return words.join(' ').trim();
|
||||
}
|
||||
return 'Unknown user';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,19 @@ extension CommandsClientExtension on Client {
|
|||
txid: args.txid,
|
||||
);
|
||||
});
|
||||
addCommand('dm', (CommandArgs args) async {
|
||||
final parts = args.msg.split(' ');
|
||||
return await args.room.client.startDirectChat(
|
||||
parts.first,
|
||||
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
||||
);
|
||||
});
|
||||
addCommand('create', (CommandArgs args) async {
|
||||
final parts = args.msg.split(' ');
|
||||
return await args.room.client.createGroupChat(
|
||||
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
||||
);
|
||||
});
|
||||
addCommand('plain', (CommandArgs args) async {
|
||||
return await args.room.sendTextEvent(
|
||||
args.msg,
|
||||
|
|
@ -202,6 +215,10 @@ extension CommandsClientExtension on Client {
|
|||
.clearOrUseOutboundGroupSession(args.room.id, wipe: true);
|
||||
return '';
|
||||
});
|
||||
addCommand('clearcache', (CommandArgs args) async {
|
||||
await clearCache();
|
||||
return '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,11 +50,13 @@ class HtmlToText {
|
|||
.firstMatch(text);
|
||||
if (match == null) {
|
||||
text = HtmlUnescape().convert(text);
|
||||
if (text[0] != '\n') {
|
||||
text = '\n$text';
|
||||
}
|
||||
if (text[text.length - 1] != '\n') {
|
||||
text += '\n';
|
||||
if (text.isNotEmpty) {
|
||||
if (text[0] != '\n') {
|
||||
text = '\n$text';
|
||||
}
|
||||
if (text[text.length - 1] != '\n') {
|
||||
text += '\n';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
|
@ -64,11 +66,13 @@ class HtmlToText {
|
|||
text = text.replaceAll(
|
||||
RegExp(r'</code>$', multiLine: false, caseSensitive: false), '');
|
||||
text = HtmlUnescape().convert(text);
|
||||
if (text[0] != '\n') {
|
||||
text = '\n$text';
|
||||
}
|
||||
if (text[text.length - 1] != '\n') {
|
||||
text += '\n';
|
||||
if (text.isNotEmpty) {
|
||||
if (text[0] != '\n') {
|
||||
text = '\n$text';
|
||||
}
|
||||
if (text[text.length - 1] != '\n') {
|
||||
text += '\n';
|
||||
}
|
||||
}
|
||||
final language =
|
||||
RegExp(r'language-(\w+)', multiLine: false, caseSensitive: false)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: matrix
|
||||
description: Matrix Dart SDK
|
||||
version: 0.7.0-nullsafety.4
|
||||
version: 0.7.0-nullsafety.10
|
||||
homepage: https://famedly.com
|
||||
|
||||
environment:
|
||||
|
|
@ -25,6 +25,7 @@ dependencies:
|
|||
collection: ^1.15.0
|
||||
webrtc_interface: ^1.0.1
|
||||
sdp_transform: ^0.3.2
|
||||
fluffybox: ^0.3.5
|
||||
|
||||
dev_dependencies:
|
||||
dart_code_metrics: ^4.4.0
|
||||
|
|
|
|||
|
|
@ -274,6 +274,28 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
test('dm', () async {
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await room.sendTextEvent('/dm @alice:example.com --no-encryption');
|
||||
expect(
|
||||
json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/r0/createRoom']?.first),
|
||||
{
|
||||
'invite': ['@alice:example.com'],
|
||||
'is_direct': true,
|
||||
'preset': 'trusted_private_chat'
|
||||
});
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await room.sendTextEvent('/create @alice:example.com --no-encryption');
|
||||
expect(
|
||||
json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/r0/createRoom']?.first),
|
||||
{'preset': 'private_chat'});
|
||||
});
|
||||
|
||||
test('discardsession', () async {
|
||||
if (olmEnabled) {
|
||||
await client.encryption?.keyManager.createOutboundGroupSession(room.id);
|
||||
|
|
@ -289,6 +311,11 @@ void main() {
|
|||
}
|
||||
});
|
||||
|
||||
test('create', () async {
|
||||
await room.sendTextEvent('/clearcache');
|
||||
expect(room.client.prevBatch, null);
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ import 'package:olm/olm.dart' as olm;
|
|||
import 'fake_database.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the ChatTime
|
||||
group('FluffyBox Database Test', () {
|
||||
testDatabase(
|
||||
getFluffyBoxDatabase(null),
|
||||
);
|
||||
});
|
||||
group('Hive Database Test', () {
|
||||
testDatabase(
|
||||
getHiveDatabase(null),
|
||||
|
|
@ -309,6 +313,42 @@ void testDatabase(
|
|||
);
|
||||
expect(olm.isEmpty, true);
|
||||
});
|
||||
test('getAllOlmSessions', () async {
|
||||
var sessions = await database.getAllOlmSessions();
|
||||
expect(sessions.isEmpty, true);
|
||||
await database.storeOlmSession(
|
||||
'identityKey',
|
||||
'sessionId',
|
||||
'pickle',
|
||||
0,
|
||||
);
|
||||
await database.storeOlmSession(
|
||||
'identityKey',
|
||||
'sessionId2',
|
||||
'pickle',
|
||||
0,
|
||||
);
|
||||
sessions = await database.getAllOlmSessions();
|
||||
expect(
|
||||
sessions,
|
||||
{
|
||||
'identityKey': {
|
||||
'sessionId': {
|
||||
'identity_key': 'identityKey',
|
||||
'pickle': 'pickle',
|
||||
'session_id': 'sessionId',
|
||||
'last_received': 0
|
||||
},
|
||||
'sessionId2': {
|
||||
'identity_key': 'identityKey',
|
||||
'pickle': 'pickle',
|
||||
'session_id': 'sessionId2',
|
||||
'last_received': 0
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
test('getOlmSessionsForDevices', () async {
|
||||
final olm = await database.getOlmSessionsForDevices(
|
||||
['identityKeys'],
|
||||
|
|
|
|||
|
|
@ -23,11 +23,25 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:matrix/src/database/hive_database.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:matrix/src/database/fluffybox_database.dart';
|
||||
|
||||
Future<DatabaseApi> getDatabase(Client? _) => getHiveDatabase(_);
|
||||
|
||||
bool hiveInitialized = false;
|
||||
|
||||
Future<FluffyBoxDatabase> getFluffyBoxDatabase(Client? c) async {
|
||||
final fileSystem = MemoryFileSystem();
|
||||
final testHivePath =
|
||||
'${fileSystem.path}/build/.test_store/${Random().nextDouble()}';
|
||||
Directory(testHivePath).createSync(recursive: true);
|
||||
final db = FluffyBoxDatabase(
|
||||
'unit_test.${c?.hashCode}',
|
||||
testHivePath,
|
||||
);
|
||||
await db.open();
|
||||
return db;
|
||||
}
|
||||
|
||||
Future<FamedlySdkHiveDatabase> getHiveDatabase(Client? c) async {
|
||||
if (!hiveInitialized) {
|
||||
final fileSystem = MemoryFileSystem();
|
||||
|
|
|
|||
|
|
@ -21,80 +21,81 @@ import 'package:test/test.dart';
|
|||
|
||||
void main() {
|
||||
group('htmlToText', () {
|
||||
test('stuff', () async {
|
||||
final testMap = <String, String>{
|
||||
'': '',
|
||||
'hello world\nthis is a test': 'hello world\nthis is a test',
|
||||
'<em>That\'s</em> not a test, <strong>this</strong> is a test':
|
||||
'*That\'s* not a test, **this** is a test',
|
||||
'Visit <del><a href="http://example.com">our website</a></del> (outdated)':
|
||||
'Visit ~~🔗our website~~ (outdated)',
|
||||
'(cw spiders) <span data-mx-spoiler>spiders are pretty cool</span>':
|
||||
'(cw spiders) ███████████████████████',
|
||||
'<span data-mx-spoiler="cw spiders">spiders are pretty cool</span>':
|
||||
'(cw spiders) ███████████████████████',
|
||||
'<img src="test.gif" alt="a test case" />': 'a test case',
|
||||
'List of cute animals:\n<ul>\n<li>Kittens</li>\n<li>Puppies</li>\n<li>Snakes<br/>(I think they\'re cute!)</li>\n</ul>\n(This list is incomplete, you can help by adding to it!)':
|
||||
'List of cute animals:\n● Kittens\n● Puppies\n● Snakes\n (I think they\'re cute!)\n(This list is incomplete, you can help by adding to it!)',
|
||||
'<em>fox</em>': '*fox*',
|
||||
'<i>fox</i>': '*fox*',
|
||||
'<strong>fox</i>': '**fox**',
|
||||
'<b>fox</b>': '**fox**',
|
||||
'<u>fox</u>': '__fox__',
|
||||
'<ins>fox</ins>': '__fox__',
|
||||
'<del>fox</del>': '~~fox~~',
|
||||
'<strike>fox</strike>': '~~fox~~',
|
||||
'<s>fox</s>': '~~fox~~',
|
||||
'<code>>fox</code>': '`>fox`',
|
||||
'<pre>meep</pre>': '```\nmeep\n```',
|
||||
'<pre>meep\n</pre>': '```\nmeep\n```',
|
||||
'<pre><code class="language-floof">meep</code></pre>':
|
||||
'```floof\nmeep\n```',
|
||||
'before<pre>code</pre>after': 'before\n```\ncode\n```\nafter',
|
||||
'<p>before</p><pre>code</pre><p>after</p>':
|
||||
'before\n```\ncode\n```\nafter',
|
||||
'<p>fox</p>': 'fox',
|
||||
'<p>fox</p><p>floof</p>': 'fox\n\nfloof',
|
||||
'<a href="https://example.org">website</a>': '🔗website',
|
||||
'<a href="https://matrix.to/#/@user:example.org">fox</a>': 'fox',
|
||||
'<a href="matrix:u/user:example.org">fox</a>': 'fox',
|
||||
'<img alt=":wave:" src="mxc://fox">': ':wave:',
|
||||
'fox<br>floof': 'fox\nfloof',
|
||||
'<blockquote>fox</blockquote>floof': '> fox\nfloof',
|
||||
'<blockquote><p>fox</p></blockquote>floof': '> fox\nfloof',
|
||||
'<blockquote><p>fox</p></blockquote><p>floof</p>': '> fox\nfloof',
|
||||
'a<blockquote>fox</blockquote>floof': 'a\n> fox\nfloof',
|
||||
'<blockquote><blockquote>fox</blockquote>floof</blockquote>fluff':
|
||||
'> > fox\n> floof\nfluff',
|
||||
'<ul><li>hey<ul><li>a</li><li>b</li></ul></li><li>foxies</li></ul>':
|
||||
'● hey\n ○ a\n ○ b\n● foxies',
|
||||
'<ol><li>a</li><li>b</li></ol>': '1. a\n2. b',
|
||||
'<ol start="42"><li>a</li><li>b</li></ol>': '42. a\n43. b',
|
||||
'<ol><li>a<ol><li>aa</li><li>bb</li></ol></li><li>b</li></ol>':
|
||||
'1. a\n 1. aa\n 2. bb\n2. b',
|
||||
'<ol><li>a<ul><li>aa</li><li>bb</li></ul></li><li>b</li></ol>':
|
||||
'1. a\n ○ aa\n ○ bb\n2. b',
|
||||
'<ul><li>a<ol><li>aa</li><li>bb</li></ol></li><li>b</li></ul>':
|
||||
'● a\n 1. aa\n 2. bb\n● b',
|
||||
'<mx-reply>bunny</mx-reply>fox': 'fox',
|
||||
'fox<hr>floof': 'fox\n----------\nfloof',
|
||||
'<p>fox</p><hr><p>floof</p>': 'fox\n----------\nfloof',
|
||||
'<h1>fox</h1>floof': '# fox\nfloof',
|
||||
'<h1>fox</h1><p>floof</p>': '# fox\nfloof',
|
||||
'floof<h1>fox</h1>': 'floof\n# fox',
|
||||
'<p>floof</p><h1>fox</h1>': 'floof\n# fox',
|
||||
'<h2>fox</h2>': '## fox',
|
||||
'<h3>fox</h3>': '### fox',
|
||||
'<h4>fox</h4>': '#### fox',
|
||||
'<h5>fox</h5>': '##### fox',
|
||||
'<h6>fox</h6>': '###### fox',
|
||||
'<span>fox</span>': 'fox',
|
||||
'<p>fox</p>\n<p>floof</p>': 'fox\n\nfloof',
|
||||
'<mx-reply>beep</mx-reply><p>fox</p>\n<p>floof</p>': 'fox\n\nfloof',
|
||||
};
|
||||
for (final entry in testMap.entries) {
|
||||
final testMap = <String, String>{
|
||||
'': '',
|
||||
'hello world\nthis is a test': 'hello world\nthis is a test',
|
||||
'<em>That\'s</em> not a test, <strong>this</strong> is a test':
|
||||
'*That\'s* not a test, **this** is a test',
|
||||
'Visit <del><a href="http://example.com">our website</a></del> (outdated)':
|
||||
'Visit ~~🔗our website~~ (outdated)',
|
||||
'(cw spiders) <span data-mx-spoiler>spiders are pretty cool</span>':
|
||||
'(cw spiders) ███████████████████████',
|
||||
'<span data-mx-spoiler="cw spiders">spiders are pretty cool</span>':
|
||||
'(cw spiders) ███████████████████████',
|
||||
'<img src="test.gif" alt="a test case" />': 'a test case',
|
||||
'List of cute animals:\n<ul>\n<li>Kittens</li>\n<li>Puppies</li>\n<li>Snakes<br/>(I think they\'re cute!)</li>\n</ul>\n(This list is incomplete, you can help by adding to it!)':
|
||||
'List of cute animals:\n● Kittens\n● Puppies\n● Snakes\n (I think they\'re cute!)\n(This list is incomplete, you can help by adding to it!)',
|
||||
'<em>fox</em>': '*fox*',
|
||||
'<i>fox</i>': '*fox*',
|
||||
'<strong>fox</i>': '**fox**',
|
||||
'<b>fox</b>': '**fox**',
|
||||
'<u>fox</u>': '__fox__',
|
||||
'<ins>fox</ins>': '__fox__',
|
||||
'<del>fox</del>': '~~fox~~',
|
||||
'<strike>fox</strike>': '~~fox~~',
|
||||
'<s>fox</s>': '~~fox~~',
|
||||
'<code>>fox</code>': '`>fox`',
|
||||
'<pre>meep</pre>': '```\nmeep\n```',
|
||||
'<pre>meep\n</pre>': '```\nmeep\n```',
|
||||
'<pre><code class="language-floof">meep</code></pre>':
|
||||
'```floof\nmeep\n```',
|
||||
'before<pre>code</pre>after': 'before\n```\ncode\n```\nafter',
|
||||
'<p>before</p><pre>code</pre><p>after</p>':
|
||||
'before\n```\ncode\n```\nafter',
|
||||
'<p>fox</p>': 'fox',
|
||||
'<p>fox</p><p>floof</p>': 'fox\n\nfloof',
|
||||
'<a href="https://example.org">website</a>': '🔗website',
|
||||
'<a href="https://matrix.to/#/@user:example.org">fox</a>': 'fox',
|
||||
'<a href="matrix:u/user:example.org">fox</a>': 'fox',
|
||||
'<img alt=":wave:" src="mxc://fox">': ':wave:',
|
||||
'fox<br>floof': 'fox\nfloof',
|
||||
'<blockquote>fox</blockquote>floof': '> fox\nfloof',
|
||||
'<blockquote><p>fox</p></blockquote>floof': '> fox\nfloof',
|
||||
'<blockquote><p>fox</p></blockquote><p>floof</p>': '> fox\nfloof',
|
||||
'a<blockquote>fox</blockquote>floof': 'a\n> fox\nfloof',
|
||||
'<blockquote><blockquote>fox</blockquote>floof</blockquote>fluff':
|
||||
'> > fox\n> floof\nfluff',
|
||||
'<ul><li>hey<ul><li>a</li><li>b</li></ul></li><li>foxies</li></ul>':
|
||||
'● hey\n ○ a\n ○ b\n● foxies',
|
||||
'<ol><li>a</li><li>b</li></ol>': '1. a\n2. b',
|
||||
'<ol start="42"><li>a</li><li>b</li></ol>': '42. a\n43. b',
|
||||
'<ol><li>a<ol><li>aa</li><li>bb</li></ol></li><li>b</li></ol>':
|
||||
'1. a\n 1. aa\n 2. bb\n2. b',
|
||||
'<ol><li>a<ul><li>aa</li><li>bb</li></ul></li><li>b</li></ol>':
|
||||
'1. a\n ○ aa\n ○ bb\n2. b',
|
||||
'<ul><li>a<ol><li>aa</li><li>bb</li></ol></li><li>b</li></ul>':
|
||||
'● a\n 1. aa\n 2. bb\n● b',
|
||||
'<mx-reply>bunny</mx-reply>fox': 'fox',
|
||||
'fox<hr>floof': 'fox\n----------\nfloof',
|
||||
'<p>fox</p><hr><p>floof</p>': 'fox\n----------\nfloof',
|
||||
'<h1>fox</h1>floof': '# fox\nfloof',
|
||||
'<h1>fox</h1><p>floof</p>': '# fox\nfloof',
|
||||
'floof<h1>fox</h1>': 'floof\n# fox',
|
||||
'<p>floof</p><h1>fox</h1>': 'floof\n# fox',
|
||||
'<h2>fox</h2>': '## fox',
|
||||
'<h3>fox</h3>': '### fox',
|
||||
'<h4>fox</h4>': '#### fox',
|
||||
'<h5>fox</h5>': '##### fox',
|
||||
'<h6>fox</h6>': '###### fox',
|
||||
'<span>fox</span>': 'fox',
|
||||
'<p>fox</p>\n<p>floof</p>': 'fox\n\nfloof',
|
||||
'<mx-reply>beep</mx-reply><p>fox</p>\n<p>floof</p>': 'fox\n\nfloof',
|
||||
'<pre><code></code></pre>': '``````',
|
||||
};
|
||||
for (final entry in testMap.entries) {
|
||||
test(entry.key, () async {
|
||||
expect(HtmlToText.convert(entry.key), entry.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,9 +234,35 @@ void main() {
|
|||
'm.relates_to': {'rel_type': 'm.replace', 'event_id': '2'},
|
||||
},
|
||||
stateKey: '',
|
||||
status: EventStatus.sending,
|
||||
),
|
||||
);
|
||||
expect(room.lastEvent?.body, 'edited cdc');
|
||||
expect(room.lastEvent?.status, EventStatus.sending);
|
||||
expect(room.lastEvent?.eventId, '4');
|
||||
|
||||
// Status update on edits working?
|
||||
room.setState(
|
||||
Event(
|
||||
senderId: '@test:example.com',
|
||||
type: 'm.room.encrypted',
|
||||
room: room,
|
||||
eventId: '5',
|
||||
unsigned: {'transaction_id': '4'},
|
||||
originServerTs: DateTime.now(),
|
||||
content: {
|
||||
'msgtype': 'm.text',
|
||||
'body': 'edited cdc',
|
||||
'm.new_content': {'msgtype': 'm.text', 'body': 'edited cdc'},
|
||||
'm.relates_to': {'rel_type': 'm.replace', 'event_id': '2'},
|
||||
},
|
||||
stateKey: '',
|
||||
status: EventStatus.sent,
|
||||
),
|
||||
);
|
||||
expect(room.lastEvent?.eventId, '5');
|
||||
expect(room.lastEvent?.body, 'edited cdc');
|
||||
expect(room.lastEvent?.status, EventStatus.sent);
|
||||
});
|
||||
test('lastEvent when reply parent edited', () async {
|
||||
room.setState(
|
||||
|
|
|
|||
Loading…
Reference in New Issue