Merge remote-tracking branch 'upstream/main' into yiffed
This commit is contained in:
commit
0f5943382e
|
@ -133,14 +133,14 @@ pages:
|
||||||
# Adds license-scanning job. Because Gitlab does not support pub.dev
|
# Adds license-scanning job. Because Gitlab does not support pub.dev
|
||||||
# we added https://github.com/oss-review-toolkit/ort
|
# we added https://github.com/oss-review-toolkit/ort
|
||||||
|
|
||||||
include:
|
#include:
|
||||||
- template: Security/License-Scanning.gitlab-ci.yml
|
# - template: Security/License-Scanning.gitlab-ci.yml
|
||||||
|
|
||||||
license_scanning:
|
#license_scanning:
|
||||||
stage: coverage
|
# stage: coverage
|
||||||
image:
|
# image:
|
||||||
name: "registry.gitlab.com/gitlab-org/security-products/analyzers/ort/ort:latest"
|
# name: "registry.gitlab.com/gitlab-org/security-products/analyzers/ort/ort:latest"
|
||||||
script:
|
# script:
|
||||||
- /opt/ort/bin/ort analyze -i $CI_PROJECT_DIR -o $CI_PROJECT_DIR/ --allow-dynamic-versions
|
# - /opt/ort/bin/ort analyze -i $CI_PROJECT_DIR -o $CI_PROJECT_DIR/ --allow-dynamic-versions
|
||||||
- /opt/ort/bin/ort scan -i $CI_PROJECT_DIR/analyzer-result.yml -o $CI_PROJECT_DIR/ || true
|
# - /opt/ort/bin/ort scan -i $CI_PROJECT_DIR/analyzer-result.yml -o $CI_PROJECT_DIR/ || true
|
||||||
- /opt/ort/bin/ort report -f GitLabLicenseModel -i $CI_PROJECT_DIR/scan-result.yml -o $CI_PROJECT_DIR/
|
# - /opt/ort/bin/ort report -f GitLabLicenseModel -i $CI_PROJECT_DIR/scan-result.yml -o $CI_PROJECT_DIR/
|
||||||
|
|
31
README.md
31
README.md
|
@ -26,45 +26,38 @@ import 'package:famedlysdk/famedlysdk.dart';
|
||||||
2. Create a new client:
|
2. Create a new client:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
Client matrix = Client("HappyChat");
|
Client client = Client("HappyChat");
|
||||||
```
|
```
|
||||||
|
|
||||||
Take a look here for an example store:
|
Take a look here for an example store:
|
||||||
[https://gitlab.com/ChristianPauly/fluffychat-flutter/snippets](https://gitlab.com/ChristianPauly/fluffychat-flutter/snippets)
|
[https://gitlab.com/ChristianPauly/fluffychat-flutter/snippets](https://gitlab.com/ChristianPauly/fluffychat-flutter/snippets)
|
||||||
|
|
||||||
```dart
|
|
||||||
Client matrix = Client("HappyChat");
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Connect to a Matrix Homeserver and listen to the streams:
|
3. Connect to a Matrix Homeserver and listen to the streams:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
matrix.onLoginStateChanged.stream.listen((bool loginState){
|
client.onLoginStateChanged.stream.listen((bool loginState){
|
||||||
print("LoginState: ${loginState.toString()}");
|
print("LoginState: ${loginState.toString()}");
|
||||||
});
|
});
|
||||||
|
|
||||||
matrix.onEvent.stream.listen((EventUpdate eventUpdate){
|
client.onEvent.stream.listen((EventUpdate eventUpdate){
|
||||||
print("New event update!");
|
print("New event update!");
|
||||||
});
|
});
|
||||||
|
|
||||||
matrix.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
client.onRoomUpdate.stream.listen((RoomUpdate eventUpdate){
|
||||||
print("New room update!");
|
print("New room update!");
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool serverValid = await matrix.checkServer("https://yourhomeserver.abc");
|
try {
|
||||||
|
await client.checkHomeserver("https://yourhomeserver.abc");
|
||||||
final bool loginValid = await matrix.login("username", "password");
|
await client.login("username", "password");
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
print('No luck...');
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Send a message to a Room:
|
4. Send a message to a Room:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final resp = await matrix.jsonRequest(
|
await client.getRoomById('your_room_id').sendTextEvent('Hello world');
|
||||||
type: "PUT",
|
|
||||||
action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId",
|
|
||||||
data: {
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"body": "hello"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -105,7 +105,7 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleEventUpdate(EventUpdate update) async {
|
Future<void> handleEventUpdate(EventUpdate update) async {
|
||||||
if (update.type == 'ephemeral') {
|
if (update.type == EventUpdateType.ephemeral) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (update.eventType.startsWith('m.key.verification.') ||
|
if (update.eventType.startsWith('m.key.verification.') ||
|
||||||
|
@ -159,17 +159,7 @@ class Encryption {
|
||||||
var haveIndex = inboundGroupSession.indexes.containsKey(messageIndexKey);
|
var haveIndex = inboundGroupSession.indexes.containsKey(messageIndexKey);
|
||||||
if (haveIndex &&
|
if (haveIndex &&
|
||||||
inboundGroupSession.indexes[messageIndexKey] != messageIndexValue) {
|
inboundGroupSession.indexes[messageIndexKey] != messageIndexValue) {
|
||||||
// TODO: maybe clear outbound session, if it is ours
|
|
||||||
// TODO: Make it so that we can't re-request the session keys, this is just for debugging
|
|
||||||
Logs.error('[Decrypt] Could not decrypt due to a corrupted session.');
|
Logs.error('[Decrypt] Could not decrypt due to a corrupted session.');
|
||||||
Logs.error('[Decrypt] Want session: $roomId $sessionId $senderKey');
|
|
||||||
Logs.error(
|
|
||||||
'[Decrypt] Have sessoin: ${inboundGroupSession.roomId} ${inboundGroupSession.sessionId} ${inboundGroupSession.senderKey}');
|
|
||||||
Logs.error(
|
|
||||||
'[Decrypt] Want indexes: $messageIndexKey $messageIndexValue');
|
|
||||||
Logs.error(
|
|
||||||
'[Decrypt] Have indexes: $messageIndexKey ${inboundGroupSession.indexes[messageIndexKey]}');
|
|
||||||
canRequestSession = true;
|
|
||||||
throw (DecryptError.CHANNEL_CORRUPTED);
|
throw (DecryptError.CHANNEL_CORRUPTED);
|
||||||
}
|
}
|
||||||
inboundGroupSession.indexes[messageIndexKey] = messageIndexValue;
|
inboundGroupSession.indexes[messageIndexKey] = messageIndexValue;
|
||||||
|
@ -188,13 +178,15 @@ class Encryption {
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
// alright, if this was actually by our own outbound group session, we might as well clear it
|
// alright, if this was actually by our own outbound group session, we might as well clear it
|
||||||
if (client.enableE2eeRecovery &&
|
if (client.enableE2eeRecovery &&
|
||||||
|
exception != DecryptError.UNKNOWN_SESSION &&
|
||||||
(keyManager
|
(keyManager
|
||||||
.getOutboundGroupSession(roomId)
|
.getOutboundGroupSession(roomId)
|
||||||
?.outboundGroupSession
|
?.outboundGroupSession
|
||||||
?.session_id() ??
|
?.session_id() ??
|
||||||
'') ==
|
'') ==
|
||||||
event.content['session_id']) {
|
event.content['session_id']) {
|
||||||
keyManager.clearOutboundGroupSession(roomId, wipe: true);
|
runInRoot(() =>
|
||||||
|
keyManager.clearOrUseOutboundGroupSession(roomId, wipe: true));
|
||||||
}
|
}
|
||||||
if (canRequestSession) {
|
if (canRequestSession) {
|
||||||
decryptedPayload = {
|
decryptedPayload = {
|
||||||
|
@ -235,8 +227,23 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Event> decryptRoomEvent(String roomId, Event event,
|
Future<Event> decryptRoomEvent(String roomId, Event event,
|
||||||
{bool store = false, String updateType = 'timeline'}) async {
|
{bool store = false,
|
||||||
final doStore = () async {
|
EventUpdateType updateType = EventUpdateType.timeline}) async {
|
||||||
|
if (event.type != EventTypes.Encrypted) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if (client.database != null &&
|
||||||
|
keyManager.getInboundGroupSession(roomId, event.content['session_id'],
|
||||||
|
event.content['sender_key']) ==
|
||||||
|
null) {
|
||||||
|
await keyManager.loadInboundGroupSession(
|
||||||
|
roomId, event.content['session_id'], event.content['sender_key']);
|
||||||
|
}
|
||||||
|
event = decryptRoomEventSync(roomId, event);
|
||||||
|
if (event.type != EventTypes.Encrypted && store) {
|
||||||
|
if (updateType != EventUpdateType.history) {
|
||||||
|
event.room?.setState(event);
|
||||||
|
}
|
||||||
await client.database?.storeEventUpdate(
|
await client.database?.storeEventUpdate(
|
||||||
client.id,
|
client.id,
|
||||||
EventUpdate(
|
EventUpdate(
|
||||||
|
@ -247,28 +254,6 @@ class Encryption {
|
||||||
sortOrder: event.sortOrder,
|
sortOrder: event.sortOrder,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (updateType != 'history') {
|
|
||||||
event.room?.setState(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (event.type != EventTypes.Encrypted) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
event = decryptRoomEventSync(roomId, event);
|
|
||||||
if (event.type != EventTypes.Encrypted) {
|
|
||||||
if (store) {
|
|
||||||
await doStore();
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
if (client.database == null) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
await keyManager.loadInboundGroupSession(
|
|
||||||
roomId, event.content['session_id'], event.content['sender_key']);
|
|
||||||
event = decryptRoomEventSync(roomId, event);
|
|
||||||
if (event.type != EventTypes.Encrypted && store) {
|
|
||||||
await doStore();
|
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
@ -288,7 +273,7 @@ class Encryption {
|
||||||
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
||||||
await keyManager.loadOutboundGroupSession(roomId);
|
await keyManager.loadOutboundGroupSession(roomId);
|
||||||
}
|
}
|
||||||
await keyManager.clearOutboundGroupSession(roomId);
|
await keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
||||||
await keyManager.createOutboundGroupSession(roomId);
|
await keyManager.createOutboundGroupSession(roomId);
|
||||||
}
|
}
|
||||||
|
@ -314,7 +299,6 @@ class Encryption {
|
||||||
'session_id': sess.outboundGroupSession.session_id(),
|
'session_id': sess.outboundGroupSession.session_id(),
|
||||||
if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
|
if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
|
||||||
};
|
};
|
||||||
sess.sentMessages++;
|
|
||||||
await keyManager.storeOutboundGroupSession(roomId, sess);
|
await keyManager.storeOutboundGroupSession(roomId, sess);
|
||||||
return encryptedPayload;
|
return encryptedPayload;
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,9 +150,14 @@ class KeyManager {
|
||||||
.markInboundGroupSessionAsUploaded(client.id, roomId, sessionId);
|
.markInboundGroupSessionAsUploaded(client.id, roomId, sessionId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO: somehow try to decrypt last message again
|
|
||||||
final room = client.getRoomById(roomId);
|
final room = client.getRoomById(roomId);
|
||||||
if (room != null) {
|
if (room != null) {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
// and finally broadcast the new session
|
||||||
room.onSessionKeyReceived.add(sessionId);
|
room.onSessionKeyReceived.add(sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +215,7 @@ class KeyManager {
|
||||||
// do e2ee recovery
|
// do e2ee recovery
|
||||||
_requestedSessionIds.add(requestIdent);
|
_requestedSessionIds.add(requestIdent);
|
||||||
unawaited(runInRoot(() =>
|
unawaited(runInRoot(() =>
|
||||||
request(room, sessionId, senderKey, askOnlyOwnDevices: true)));
|
request(room, sessionId, senderKey, onlineKeyBackupOnly: true)));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -226,6 +231,18 @@ class KeyManager {
|
||||||
return sess;
|
return sess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Map<String, bool>> _getDeviceKeyIdMap(
|
||||||
|
List<DeviceKeys> deviceKeys) {
|
||||||
|
final deviceKeyIds = <String, Map<String, bool>>{};
|
||||||
|
for (final device in deviceKeys) {
|
||||||
|
if (!deviceKeyIds.containsKey(device.userId)) {
|
||||||
|
deviceKeyIds[device.userId] = <String, bool>{};
|
||||||
|
}
|
||||||
|
deviceKeyIds[device.userId][device.deviceId] = device.blocked;
|
||||||
|
}
|
||||||
|
return deviceKeyIds;
|
||||||
|
}
|
||||||
|
|
||||||
/// clear all cached inbound group sessions. useful for testing
|
/// clear all cached inbound group sessions. useful for testing
|
||||||
void clearOutboundGroupSessions() {
|
void clearOutboundGroupSessions() {
|
||||||
_outboundGroupSessions.clear();
|
_outboundGroupSessions.clear();
|
||||||
|
@ -233,8 +250,8 @@ class KeyManager {
|
||||||
|
|
||||||
/// Clears the existing outboundGroupSession but first checks if the participating
|
/// Clears the existing outboundGroupSession but first checks if the participating
|
||||||
/// devices have been changed. Returns false if the session has not been cleared because
|
/// devices have been changed. Returns false if the session has not been cleared because
|
||||||
/// it wasn't necessary.
|
/// it wasn't necessary. Otherwise returns true.
|
||||||
Future<bool> clearOutboundGroupSession(String roomId,
|
Future<bool> clearOrUseOutboundGroupSession(String roomId,
|
||||||
{bool wipe = false}) async {
|
{bool wipe = false}) async {
|
||||||
final room = client.getRoomById(roomId);
|
final room = client.getRoomById(roomId);
|
||||||
final sess = getOutboundGroupSession(roomId);
|
final sess = getOutboundGroupSession(roomId);
|
||||||
|
@ -242,15 +259,7 @@ class KeyManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!wipe) {
|
if (!wipe) {
|
||||||
// first check if the devices in the room changed
|
// first check if it needs to be rotated
|
||||||
final deviceKeys = await room.getUserDeviceKeys();
|
|
||||||
deviceKeys.removeWhere((k) => k.blocked);
|
|
||||||
final deviceKeyIds = deviceKeys.map((k) => k.deviceId).toList();
|
|
||||||
deviceKeyIds.sort();
|
|
||||||
if (deviceKeyIds.toString() != sess.devices.toString()) {
|
|
||||||
wipe = true;
|
|
||||||
}
|
|
||||||
// next check if it needs to be rotated
|
|
||||||
final encryptionContent = room.getState(EventTypes.Encryption)?.content;
|
final encryptionContent = room.getState(EventTypes.Encryption)?.content;
|
||||||
final maxMessages = encryptionContent != null &&
|
final maxMessages = encryptionContent != null &&
|
||||||
encryptionContent['rotation_period_msgs'] is int
|
encryptionContent['rotation_period_msgs'] is int
|
||||||
|
@ -266,7 +275,76 @@ class KeyManager {
|
||||||
.isBefore(DateTime.now())) {
|
.isBefore(DateTime.now())) {
|
||||||
wipe = true;
|
wipe = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!wipe) {
|
if (!wipe) {
|
||||||
|
// next check if the devices in the room changed
|
||||||
|
final devicesToReceive = <DeviceKeys>[];
|
||||||
|
final newDeviceKeys = await room.getUserDeviceKeys();
|
||||||
|
final newDeviceKeyIds = _getDeviceKeyIdMap(newDeviceKeys);
|
||||||
|
// first check for user differences
|
||||||
|
final oldUserIds = Set.from(sess.devices.keys);
|
||||||
|
final newUserIds = Set.from(newDeviceKeyIds.keys);
|
||||||
|
if (oldUserIds.difference(newUserIds).isNotEmpty) {
|
||||||
|
// a user left the room, we must wipe the session
|
||||||
|
wipe = true;
|
||||||
|
} else {
|
||||||
|
final newUsers = newUserIds.difference(oldUserIds);
|
||||||
|
if (newUsers.isNotEmpty) {
|
||||||
|
// new user! Gotta send the megolm session to them
|
||||||
|
devicesToReceive
|
||||||
|
.addAll(newDeviceKeys.where((d) => newUsers.contains(d.userId)));
|
||||||
|
}
|
||||||
|
// okay, now we must test all the individual user devices, if anything new got blocked
|
||||||
|
// or if we need to send to any new devices.
|
||||||
|
// for this it is enough if we iterate over the old user Ids, as the new ones already have the needed keys in the list.
|
||||||
|
// we also know that all the old user IDs appear in the old one, else we have already wiped the session
|
||||||
|
for (final userId in oldUserIds) {
|
||||||
|
final oldBlockedDevices = Set.from(sess.devices[userId].entries
|
||||||
|
.where((e) => e.value)
|
||||||
|
.map((e) => e.key));
|
||||||
|
final newBlockedDevices = Set.from(newDeviceKeyIds[userId]
|
||||||
|
.entries
|
||||||
|
.where((e) => e.value)
|
||||||
|
.map((e) => e.key));
|
||||||
|
// we don't really care about old devices that got dropped (deleted), we only care if new ones got added and if new ones got blocked
|
||||||
|
// check if new devices got blocked
|
||||||
|
if (newBlockedDevices.difference(oldBlockedDevices).isNotEmpty) {
|
||||||
|
wipe = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// and now add all the new devices!
|
||||||
|
final oldDeviceIds = Set.from(sess.devices[userId].keys);
|
||||||
|
final newDeviceIds = Set.from(newDeviceKeyIds[userId].keys);
|
||||||
|
final newDevices = newDeviceIds.difference(oldDeviceIds);
|
||||||
|
if (newDeviceIds.isNotEmpty) {
|
||||||
|
devicesToReceive.addAll(newDeviceKeys.where(
|
||||||
|
(d) => d.userId == userId && newDevices.contains(d.deviceId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wipe) {
|
||||||
|
// okay, we use the outbound group session!
|
||||||
|
sess.sentMessages++;
|
||||||
|
sess.devices = newDeviceKeyIds;
|
||||||
|
final rawSession = <String, dynamic>{
|
||||||
|
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||||
|
'room_id': room.id,
|
||||||
|
'session_id': sess.outboundGroupSession.session_id(),
|
||||||
|
'session_key': sess.outboundGroupSession.session_key(),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
devicesToReceive.removeWhere((k) => k.blocked);
|
||||||
|
if (devicesToReceive.isNotEmpty) {
|
||||||
|
await client.sendToDeviceEncrypted(
|
||||||
|
devicesToReceive, 'm.room_key', rawSession);
|
||||||
|
}
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs.error(
|
||||||
|
'[LibOlm] Unable to re-send the session key at later index to new devices: ' +
|
||||||
|
e.toString(),
|
||||||
|
s);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,15 +369,13 @@ class KeyManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OutboundGroupSession> createOutboundGroupSession(String roomId) async {
|
Future<OutboundGroupSession> createOutboundGroupSession(String roomId) async {
|
||||||
await clearOutboundGroupSession(roomId, wipe: true);
|
await clearOrUseOutboundGroupSession(roomId, wipe: true);
|
||||||
final room = client.getRoomById(roomId);
|
final room = client.getRoomById(roomId);
|
||||||
if (room == null) {
|
if (room == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final deviceKeys = await room.getUserDeviceKeys();
|
final deviceKeys = await room.getUserDeviceKeys();
|
||||||
deviceKeys.removeWhere((k) => k.blocked);
|
final deviceKeyIds = _getDeviceKeyIdMap(deviceKeys);
|
||||||
final deviceKeyIds = deviceKeys.map((k) => k.deviceId).toList();
|
|
||||||
deviceKeyIds.sort();
|
|
||||||
final outboundGroupSession = olm.OutboundGroupSession();
|
final outboundGroupSession = olm.OutboundGroupSession();
|
||||||
try {
|
try {
|
||||||
outboundGroupSession.create();
|
outboundGroupSession.create();
|
||||||
|
@ -326,6 +402,7 @@ class KeyManager {
|
||||||
key: client.userID,
|
key: client.userID,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
deviceKeys.removeWhere((k) => k.blocked);
|
||||||
await client.sendToDeviceEncrypted(deviceKeys, 'm.room_key', rawSession);
|
await client.sendToDeviceEncrypted(deviceKeys, 'm.room_key', rawSession);
|
||||||
await storeOutboundGroupSession(roomId, sess);
|
await storeOutboundGroupSession(roomId, sess);
|
||||||
_outboundGroupSessions[roomId] = sess;
|
_outboundGroupSessions[roomId] = sess;
|
||||||
|
@ -467,7 +544,7 @@ class KeyManager {
|
||||||
String sessionId,
|
String sessionId,
|
||||||
String senderKey, {
|
String senderKey, {
|
||||||
bool tryOnlineBackup = true,
|
bool tryOnlineBackup = true,
|
||||||
bool askOnlyOwnDevices = false,
|
bool onlineKeyBackupOnly = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (tryOnlineBackup && await isCached()) {
|
if (tryOnlineBackup && await isCached()) {
|
||||||
// let's first check our online key backup store thingy...
|
// let's first check our online key backup store thingy...
|
||||||
|
@ -491,13 +568,13 @@ class KeyManager {
|
||||||
return; // we managed to load the session from online backup, no need to care about it now
|
return; // we managed to load the session from online backup, no need to care about it now
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (onlineKeyBackupOnly) {
|
||||||
|
return; // we only want to do the online key backup
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// while we just send the to-device event to '*', we still need to save the
|
// while we just send the to-device event to '*', we still need to save the
|
||||||
// devices themself to know where to send the cancel to after receiving a reply
|
// devices themself to know where to send the cancel to after receiving a reply
|
||||||
final devices = await room.getUserDeviceKeys();
|
final devices = await room.getUserDeviceKeys();
|
||||||
if (askOnlyOwnDevices) {
|
|
||||||
devices.removeWhere((d) => d.userId != client.userID);
|
|
||||||
}
|
|
||||||
final requestId = client.generateUniqueTransactionId();
|
final requestId = client.generateUniqueTransactionId();
|
||||||
final request = KeyManagerKeyShareRequest(
|
final request = KeyManagerKeyShareRequest(
|
||||||
requestId: requestId,
|
requestId: requestId,
|
||||||
|
|
|
@ -24,7 +24,11 @@ import '../../src/database/database.dart' show DbOutboundGroupSession;
|
||||||
import '../../src/utils/logs.dart';
|
import '../../src/utils/logs.dart';
|
||||||
|
|
||||||
class OutboundGroupSession {
|
class OutboundGroupSession {
|
||||||
List<String> devices;
|
/// The devices is a map from user id to device id to if the device is blocked.
|
||||||
|
/// This way we can easily know if a new user is added, leaves, a new devices is added, and,
|
||||||
|
/// very importantly, if we block a device. These are all important for determining if/when
|
||||||
|
/// an outbound session needs to be rotated.
|
||||||
|
Map<String, Map<String, bool>> devices;
|
||||||
DateTime creationTime;
|
DateTime creationTime;
|
||||||
olm.OutboundGroupSession outboundGroupSession;
|
olm.OutboundGroupSession outboundGroupSession;
|
||||||
int sentMessages;
|
int sentMessages;
|
||||||
|
@ -40,10 +44,21 @@ class OutboundGroupSession {
|
||||||
|
|
||||||
OutboundGroupSession.fromDb(DbOutboundGroupSession dbEntry, String key)
|
OutboundGroupSession.fromDb(DbOutboundGroupSession dbEntry, String key)
|
||||||
: key = key {
|
: key = key {
|
||||||
|
try {
|
||||||
|
devices = {};
|
||||||
|
for (final entry in json.decode(dbEntry.deviceIds).entries) {
|
||||||
|
devices[entry.key] = Map<String, bool>.from(entry.value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// devices is bad (old data), so just not use this session
|
||||||
|
Logs.info(
|
||||||
|
'[OutboundGroupSession] Session in database is old, not using it. ' +
|
||||||
|
e.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
outboundGroupSession = olm.OutboundGroupSession();
|
outboundGroupSession = olm.OutboundGroupSession();
|
||||||
try {
|
try {
|
||||||
outboundGroupSession.unpickle(key, dbEntry.pickle);
|
outboundGroupSession.unpickle(key, dbEntry.pickle);
|
||||||
devices = List<String>.from(json.decode(dbEntry.deviceIds));
|
|
||||||
creationTime = DateTime.fromMillisecondsSinceEpoch(dbEntry.creationTime);
|
creationTime = DateTime.fromMillisecondsSinceEpoch(dbEntry.creationTime);
|
||||||
sentMessages = dbEntry.sentMessages;
|
sentMessages = dbEntry.sentMessages;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
|
@ -256,43 +256,51 @@ class Client extends MatrixApi {
|
||||||
return wellKnown;
|
return wellKnown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the supported versions of the Matrix protocol and the supported
|
@Deprecated('Use [checkHomeserver] instead.')
|
||||||
/// login types. Returns false if the server is not compatible with the
|
|
||||||
/// client.
|
|
||||||
/// Throws FormatException, TimeoutException and MatrixException on error.
|
|
||||||
Future<bool> checkServer(dynamic serverUrl) async {
|
Future<bool> checkServer(dynamic serverUrl) async {
|
||||||
try {
|
try {
|
||||||
if (serverUrl is Uri) {
|
await checkHomeserver(serverUrl);
|
||||||
homeserver = serverUrl;
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the supported versions of the Matrix protocol and the supported
|
||||||
|
/// login types. Throws an exception if the server is not compatible with the
|
||||||
|
/// client and sets [homeserver] to [serverUrl] if it is. Supports the types [Uri]
|
||||||
|
/// and [String].
|
||||||
|
Future<void> checkHomeserver(dynamic homeserverUrl,
|
||||||
|
{Set<String> supportedLoginTypes = supportedLoginTypes}) async {
|
||||||
|
try {
|
||||||
|
if (homeserverUrl is Uri) {
|
||||||
|
homeserver = homeserverUrl;
|
||||||
} else {
|
} else {
|
||||||
// URLs allow to have whitespace surrounding them, see https://www.w3.org/TR/2011/WD-html5-20110525/urls.html
|
// URLs allow to have whitespace surrounding them, see https://www.w3.org/TR/2011/WD-html5-20110525/urls.html
|
||||||
// As we want to strip a trailing slash, though, we have to trim the url ourself
|
// As we want to strip a trailing slash, though, we have to trim the url ourself
|
||||||
// and thus can't let Uri.parse() deal with it.
|
// and thus can't let Uri.parse() deal with it.
|
||||||
serverUrl = serverUrl.trim();
|
homeserverUrl = homeserverUrl.trim();
|
||||||
// strip a trailing slash
|
// strip a trailing slash
|
||||||
if (serverUrl.endsWith('/')) {
|
if (homeserverUrl.endsWith('/')) {
|
||||||
serverUrl = serverUrl.substring(0, serverUrl.length - 1);
|
homeserverUrl = homeserverUrl.substring(0, homeserverUrl.length - 1);
|
||||||
}
|
}
|
||||||
homeserver = Uri.parse(serverUrl);
|
homeserver = Uri.parse(homeserverUrl);
|
||||||
}
|
}
|
||||||
final versions = await requestSupportedVersions();
|
final versions = await requestSupportedVersions();
|
||||||
|
|
||||||
for (var i = 0; i < versions.versions.length; i++) {
|
if (!versions.versions
|
||||||
if (versions.versions[i] == 'r0.5.0' ||
|
.any((version) => supportedVersions.contains(version))) {
|
||||||
versions.versions[i] == 'r0.6.0') {
|
throw Exception(
|
||||||
break;
|
'Server supports the versions: ${versions.versions.toString()} but this application is only compatible with ${supportedVersions.toString()}.');
|
||||||
} else if (i == versions.versions.length - 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final loginTypes = await requestLoginTypes();
|
final loginTypes = await requestLoginTypes();
|
||||||
if (loginTypes.flows.indexWhere((f) => f.type == 'm.login.password') ==
|
if (!loginTypes.flows.any((f) => supportedLoginTypes.contains(f.type))) {
|
||||||
-1) {
|
throw Exception(
|
||||||
return false;
|
'Server supports the Login Types: ${loginTypes.flows.map((f) => f.toJson).toList().toString()} but this application is only compatible with ${supportedLoginTypes.toString()}.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
homeserver = null;
|
homeserver = null;
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -301,7 +309,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
/// Checks to see if a username is available, and valid, for the server.
|
/// Checks to see if a username is available, and valid, for the server.
|
||||||
/// Returns the fully-qualified Matrix user ID (MXID) that has been registered.
|
/// Returns the fully-qualified Matrix user ID (MXID) that has been registered.
|
||||||
/// You have to call [checkServer] first to set a homeserver.
|
/// You have to call [checkHomeserver] first to set a homeserver.
|
||||||
@override
|
@override
|
||||||
Future<LoginResponse> register({
|
Future<LoginResponse> register({
|
||||||
String username,
|
String username,
|
||||||
|
@ -339,7 +347,7 @@ class Client extends MatrixApi {
|
||||||
/// Handles the login and allows the client to call all APIs which require
|
/// Handles the login and allows the client to call all APIs which require
|
||||||
/// authentication. Returns false if the login was not successful. Throws
|
/// authentication. Returns false if the login was not successful. Throws
|
||||||
/// MatrixException if login was not successful.
|
/// MatrixException if login was not successful.
|
||||||
/// You have to call [checkServer] first to set a homeserver.
|
/// You have to call [checkHomeserver] first to set a homeserver.
|
||||||
@override
|
@override
|
||||||
Future<LoginResponse> login({
|
Future<LoginResponse> login({
|
||||||
String type = 'm.login.password',
|
String type = 'm.login.password',
|
||||||
|
@ -503,8 +511,11 @@ class Client extends MatrixApi {
|
||||||
? PushRuleSet.fromJson(accountData['m.push_rules'].content)
|
? PushRuleSet.fromJson(accountData['m.push_rules'].content)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}';
|
static const Set<String> supportedVersions = {'r0.5.0', 'r0.6.0'};
|
||||||
static String messagesFilters = '{"lazy_load_members":true}';
|
static const Set<String> supportedLoginTypes = {'m.login.password'};
|
||||||
|
static const String syncFilters =
|
||||||
|
'{"room":{"state":{"lazy_load_members":true}}}';
|
||||||
|
static const String messagesFilters = '{"lazy_load_members":true}';
|
||||||
static const List<String> supportedDirectEncryptionAlgorithms = [
|
static const List<String> supportedDirectEncryptionAlgorithms = [
|
||||||
'm.olm.v1.curve25519-aes-sha2'
|
'm.olm.v1.curve25519-aes-sha2'
|
||||||
];
|
];
|
||||||
|
@ -911,14 +922,16 @@ class Client extends MatrixApi {
|
||||||
if (room.state?.isNotEmpty ?? false) {
|
if (room.state?.isNotEmpty ?? false) {
|
||||||
// TODO: This method seems to be comperatively slow for some updates
|
// TODO: This method seems to be comperatively slow for some updates
|
||||||
await _handleRoomEvents(
|
await _handleRoomEvents(
|
||||||
id, room.state.map((i) => i.toJson()).toList(), 'state');
|
id,
|
||||||
|
room.state.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.state);
|
||||||
handledEvents = true;
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
if (room.timeline?.events?.isNotEmpty ?? false) {
|
if (room.timeline?.events?.isNotEmpty ?? false) {
|
||||||
await _handleRoomEvents(
|
await _handleRoomEvents(
|
||||||
id,
|
id,
|
||||||
room.timeline.events.map((i) => i.toJson()).toList(),
|
room.timeline.events.map((i) => i.toJson()).toList(),
|
||||||
sortAtTheEnd ? 'history' : 'timeline',
|
sortAtTheEnd ? EventUpdateType.history : EventUpdateType.timeline,
|
||||||
sortAtTheEnd: sortAtTheEnd);
|
sortAtTheEnd: sortAtTheEnd);
|
||||||
handledEvents = true;
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
|
@ -928,30 +941,40 @@ class Client extends MatrixApi {
|
||||||
id, room.ephemeral.map((i) => i.toJson()).toList());
|
id, room.ephemeral.map((i) => i.toJson()).toList());
|
||||||
}
|
}
|
||||||
if (room.accountData?.isNotEmpty ?? false) {
|
if (room.accountData?.isNotEmpty ?? false) {
|
||||||
await _handleRoomEvents(id,
|
await _handleRoomEvents(
|
||||||
room.accountData.map((i) => i.toJson()).toList(), 'account_data');
|
id,
|
||||||
|
room.accountData.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.accountData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (room is LeftRoomUpdate) {
|
if (room is LeftRoomUpdate) {
|
||||||
if (room.timeline?.events?.isNotEmpty ?? false) {
|
if (room.timeline?.events?.isNotEmpty ?? false) {
|
||||||
await _handleRoomEvents(id,
|
await _handleRoomEvents(
|
||||||
room.timeline.events.map((i) => i.toJson()).toList(), 'timeline');
|
id,
|
||||||
|
room.timeline.events.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.timeline);
|
||||||
handledEvents = true;
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
if (room.accountData?.isNotEmpty ?? false) {
|
if (room.accountData?.isNotEmpty ?? false) {
|
||||||
await _handleRoomEvents(id,
|
await _handleRoomEvents(
|
||||||
room.accountData.map((i) => i.toJson()).toList(), 'account_data');
|
id,
|
||||||
|
room.accountData.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.accountData);
|
||||||
}
|
}
|
||||||
if (room.state?.isNotEmpty ?? false) {
|
if (room.state?.isNotEmpty ?? false) {
|
||||||
await _handleRoomEvents(
|
await _handleRoomEvents(
|
||||||
id, room.state.map((i) => i.toJson()).toList(), 'state');
|
id,
|
||||||
|
room.state.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.state);
|
||||||
handledEvents = true;
|
handledEvents = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (room is InvitedRoomUpdate &&
|
if (room is InvitedRoomUpdate &&
|
||||||
(room.inviteState?.isNotEmpty ?? false)) {
|
(room.inviteState?.isNotEmpty ?? false)) {
|
||||||
await _handleRoomEvents(id,
|
await _handleRoomEvents(
|
||||||
room.inviteState.map((i) => i.toJson()).toList(), 'invite_state');
|
id,
|
||||||
|
room.inviteState.map((i) => i.toJson()).toList(),
|
||||||
|
EventUpdateType.inviteState);
|
||||||
}
|
}
|
||||||
if (handledEvents && database != null && roomObj != null) {
|
if (handledEvents && database != null && roomObj != null) {
|
||||||
await roomObj.updateSortOrder();
|
await roomObj.updateSortOrder();
|
||||||
|
@ -961,7 +984,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
Future<void> _handleEphemerals(String id, List<dynamic> events) async {
|
Future<void> _handleEphemerals(String id, List<dynamic> events) async {
|
||||||
for (num i = 0; i < events.length; i++) {
|
for (num i = 0; i < events.length; i++) {
|
||||||
await _handleEvent(events[i], id, 'ephemeral');
|
await _handleEvent(events[i], id, EventUpdateType.ephemeral);
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -998,13 +1021,13 @@ class Client extends MatrixApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events[i]['content'] = receiptStateContent;
|
events[i]['content'] = receiptStateContent;
|
||||||
await _handleEvent(events[i], id, 'account_data');
|
await _handleEvent(events[i], id, EventUpdateType.accountData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleRoomEvents(
|
Future<void> _handleRoomEvents(
|
||||||
String chat_id, List<dynamic> events, String type,
|
String chat_id, List<dynamic> events, EventUpdateType type,
|
||||||
{bool sortAtTheEnd = false}) async {
|
{bool sortAtTheEnd = false}) async {
|
||||||
for (num i = 0; i < events.length; i++) {
|
for (num i = 0; i < events.length; i++) {
|
||||||
await _handleEvent(events[i], chat_id, type, sortAtTheEnd: sortAtTheEnd);
|
await _handleEvent(events[i], chat_id, type, sortAtTheEnd: sortAtTheEnd);
|
||||||
|
@ -1012,7 +1035,7 @@ class Client extends MatrixApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleEvent(
|
Future<void> _handleEvent(
|
||||||
Map<String, dynamic> event, String roomID, String type,
|
Map<String, dynamic> event, String roomID, EventUpdateType type,
|
||||||
{bool sortAtTheEnd = false}) async {
|
{bool sortAtTheEnd = false}) async {
|
||||||
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
|
||||||
|
@ -1028,7 +1051,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
// ephemeral events aren't persisted and don't need a sort order - they are
|
// ephemeral events aren't persisted and don't need a sort order - they are
|
||||||
// expected to be processed as soon as they come in
|
// expected to be processed as soon as they come in
|
||||||
final sortOrder = type != 'ephemeral'
|
final sortOrder = type != EventUpdateType.ephemeral
|
||||||
? (sortAtTheEnd ? room.oldSortOrder : room.newSortOrder)
|
? (sortAtTheEnd ? room.oldSortOrder : room.newSortOrder)
|
||||||
: 0.0;
|
: 0.0;
|
||||||
var update = EventUpdate(
|
var update = EventUpdate(
|
||||||
|
@ -1051,7 +1074,7 @@ class Client extends MatrixApi {
|
||||||
room.setState(user);
|
room.setState(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type != 'ephemeral' && database != null) {
|
if (type != EventUpdateType.ephemeral && database != null) {
|
||||||
await database.storeEventUpdate(id, update);
|
await database.storeEventUpdate(id, update);
|
||||||
}
|
}
|
||||||
_updateRoomsByEventUpdate(update);
|
_updateRoomsByEventUpdate(update);
|
||||||
|
@ -1062,7 +1085,7 @@ class Client extends MatrixApi {
|
||||||
|
|
||||||
final rawUnencryptedEvent = update.content;
|
final rawUnencryptedEvent = update.content;
|
||||||
|
|
||||||
if (prevBatch != null && type == 'timeline') {
|
if (prevBatch != null && type == EventUpdateType.timeline) {
|
||||||
if (rawUnencryptedEvent['type'] == EventTypes.CallInvite) {
|
if (rawUnencryptedEvent['type'] == EventTypes.CallInvite) {
|
||||||
onCallInvite
|
onCallInvite
|
||||||
.add(Event.fromJson(rawUnencryptedEvent, room, sortOrder));
|
.add(Event.fromJson(rawUnencryptedEvent, room, sortOrder));
|
||||||
|
@ -1141,15 +1164,15 @@ class Client extends MatrixApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateRoomsByEventUpdate(EventUpdate eventUpdate) {
|
void _updateRoomsByEventUpdate(EventUpdate eventUpdate) {
|
||||||
if (eventUpdate.type == 'history') return;
|
if (eventUpdate.type == EventUpdateType.history) return;
|
||||||
|
|
||||||
final room = getRoomById(eventUpdate.roomID);
|
final room = getRoomById(eventUpdate.roomID);
|
||||||
if (room == null) return;
|
if (room == null) return;
|
||||||
|
|
||||||
switch (eventUpdate.type) {
|
switch (eventUpdate.type) {
|
||||||
case 'timeline':
|
case EventUpdateType.timeline:
|
||||||
case 'state':
|
case EventUpdateType.state:
|
||||||
case 'invite_state':
|
case EventUpdateType.inviteState:
|
||||||
var stateEvent =
|
var stateEvent =
|
||||||
Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
||||||
var prevState = room.getState(stateEvent.type, stateEvent.stateKey);
|
var prevState = room.getState(stateEvent.type, stateEvent.stateKey);
|
||||||
|
@ -1175,14 +1198,16 @@ sort order of ${prevState.sortOrder}. This should never happen...''');
|
||||||
room.setState(stateEvent);
|
room.setState(stateEvent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'account_data':
|
case EventUpdateType.accountData:
|
||||||
room.roomAccountData[eventUpdate.eventType] =
|
room.roomAccountData[eventUpdate.eventType] =
|
||||||
BasicRoomEvent.fromJson(eventUpdate.content);
|
BasicRoomEvent.fromJson(eventUpdate.content);
|
||||||
break;
|
break;
|
||||||
case 'ephemeral':
|
case EventUpdateType.ephemeral:
|
||||||
room.ephemerals[eventUpdate.eventType] =
|
room.ephemerals[eventUpdate.eventType] =
|
||||||
BasicRoomEvent.fromJson(eventUpdate.content);
|
BasicRoomEvent.fromJson(eventUpdate.content);
|
||||||
break;
|
break;
|
||||||
|
case EventUpdateType.history:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
room.onUpdate.add(room.id);
|
room.onUpdate.add(room.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:olm/olm.dart' as olm;
|
||||||
|
|
||||||
import '../../famedlysdk.dart' as sdk;
|
import '../../famedlysdk.dart' as sdk;
|
||||||
import '../../matrix_api.dart' as api;
|
import '../../matrix_api.dart' as api;
|
||||||
import '../../matrix_api.dart';
|
|
||||||
import '../client.dart';
|
import '../client.dart';
|
||||||
import '../room.dart';
|
import '../room.dart';
|
||||||
import '../utils/logs.dart';
|
import '../utils/logs.dart';
|
||||||
|
@ -266,13 +265,13 @@ class Database extends _$Database {
|
||||||
// let's see if we need any m.room.member events
|
// let's see if we need any m.room.member events
|
||||||
final membersToPostload = <String>{};
|
final membersToPostload = <String>{};
|
||||||
// the lastEvent message preview might have an author we need to fetch, if it is a group chat
|
// the lastEvent message preview might have an author we need to fetch, if it is a group chat
|
||||||
if (room.getState(EventTypes.Message) != null && !room.isDirectChat) {
|
if (room.getState(api.EventTypes.Message) != null && !room.isDirectChat) {
|
||||||
membersToPostload.add(room.getState(EventTypes.Message).senderId);
|
membersToPostload.add(room.getState(api.EventTypes.Message).senderId);
|
||||||
}
|
}
|
||||||
// if the room has no name and no canonical alias, its name is calculated
|
// if the room has no name and no canonical alias, its name is calculated
|
||||||
// based on the heroes of the room
|
// based on the heroes of the room
|
||||||
if (room.getState(EventTypes.RoomName) == null &&
|
if (room.getState(api.EventTypes.RoomName) == null &&
|
||||||
room.getState(EventTypes.RoomCanonicalAlias) == null &&
|
room.getState(api.EventTypes.RoomCanonicalAlias) == null &&
|
||||||
room.mHeroes != null) {
|
room.mHeroes != null) {
|
||||||
// we don't have a name and no canonical alias, so we'll need to
|
// we don't have a name and no canonical alias, so we'll need to
|
||||||
// post-load the heroes
|
// post-load the heroes
|
||||||
|
@ -430,7 +429,7 @@ class Database extends _$Database {
|
||||||
/// [transaction].
|
/// [transaction].
|
||||||
Future<void> storeEventUpdate(
|
Future<void> storeEventUpdate(
|
||||||
int clientId, sdk.EventUpdate eventUpdate) async {
|
int clientId, sdk.EventUpdate eventUpdate) async {
|
||||||
if (eventUpdate.type == 'ephemeral') return;
|
if (eventUpdate.type == sdk.EventUpdateType.ephemeral) return;
|
||||||
final eventContent = eventUpdate.content;
|
final eventContent = eventUpdate.content;
|
||||||
final type = eventUpdate.type;
|
final type = eventUpdate.type;
|
||||||
final chatId = eventUpdate.roomID;
|
final chatId = eventUpdate.roomID;
|
||||||
|
@ -441,11 +440,12 @@ class Database extends _$Database {
|
||||||
stateKey = eventContent['state_key'];
|
stateKey = eventContent['state_key'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventUpdate.eventType == EventTypes.Redaction) {
|
if (eventUpdate.eventType == api.EventTypes.Redaction) {
|
||||||
await redactMessage(clientId, eventUpdate);
|
await redactMessage(clientId, eventUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == 'timeline' || type == 'history') {
|
if (type == sdk.EventUpdateType.timeline ||
|
||||||
|
type == sdk.EventUpdateType.history) {
|
||||||
// calculate the status
|
// calculate the status
|
||||||
var status = 2;
|
var status = 2;
|
||||||
if (eventContent['unsigned'] is Map<String, dynamic> &&
|
if (eventContent['unsigned'] is Map<String, dynamic> &&
|
||||||
|
@ -493,7 +493,7 @@ class Database extends _$Database {
|
||||||
}
|
}
|
||||||
if (storeNewEvent) {
|
if (storeNewEvent) {
|
||||||
DbEvent oldEvent;
|
DbEvent oldEvent;
|
||||||
if (type == 'history') {
|
if (type == sdk.EventUpdateType.history) {
|
||||||
final allOldEvents =
|
final allOldEvents =
|
||||||
await getEvent(clientId, eventContent['event_id'], chatId).get();
|
await getEvent(clientId, eventContent['event_id'], chatId).get();
|
||||||
if (allOldEvents.isNotEmpty) {
|
if (allOldEvents.isNotEmpty) {
|
||||||
|
@ -527,12 +527,15 @@ class Database extends _$Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == 'history') return;
|
if (type == sdk.EventUpdateType.history) return;
|
||||||
|
|
||||||
if (type != 'account_data' &&
|
if (type != sdk.EventUpdateType.accountData &&
|
||||||
((stateKey is String) ||
|
((stateKey is String) ||
|
||||||
[EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted]
|
[
|
||||||
.contains(eventUpdate.eventType))) {
|
api.EventTypes.Message,
|
||||||
|
api.EventTypes.Sticker,
|
||||||
|
api.EventTypes.Encrypted
|
||||||
|
].contains(eventUpdate.eventType))) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
await storeRoomState(
|
await storeRoomState(
|
||||||
clientId,
|
clientId,
|
||||||
|
@ -547,7 +550,7 @@ class Database extends _$Database {
|
||||||
json.encode(eventContent['prev_content'] ?? ''),
|
json.encode(eventContent['prev_content'] ?? ''),
|
||||||
stateKey ?? '',
|
stateKey ?? '',
|
||||||
);
|
);
|
||||||
} else if (type == 'account_data') {
|
} else if (type == sdk.EventUpdateType.accountData) {
|
||||||
await storeRoomAccountData(
|
await storeRoomAccountData(
|
||||||
clientId,
|
clientId,
|
||||||
eventContent['type'],
|
eventContent['type'],
|
||||||
|
|
|
@ -316,7 +316,7 @@ class Event extends MatrixEvent {
|
||||||
|
|
||||||
room.client.onEvent.add(EventUpdate(
|
room.client.onEvent.add(EventUpdate(
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
eventType: type,
|
eventType: type,
|
||||||
content: {
|
content: {
|
||||||
'event_id': eventId,
|
'event_id': eventId,
|
||||||
|
|
|
@ -272,7 +272,15 @@ class Room {
|
||||||
|
|
||||||
var lastEvent = lastEvents.isEmpty
|
var lastEvent = lastEvents.isEmpty
|
||||||
? null
|
? null
|
||||||
: lastEvents.reduce((a, b) => a.sortOrder > b.sortOrder ? a : b);
|
: lastEvents.reduce((a, b) {
|
||||||
|
if (a.sortOrder == b.sortOrder) {
|
||||||
|
// if two events have the same sort order we want to give encrypted events a lower priority
|
||||||
|
// This is so that if the same event exists in the state both encrypted *and* unencrypted,
|
||||||
|
// the unencrypted one is picked
|
||||||
|
return a.type == EventTypes.Encrypted ? b : a;
|
||||||
|
}
|
||||||
|
return a.sortOrder > b.sortOrder ? a : b;
|
||||||
|
});
|
||||||
if (lastEvent == null) {
|
if (lastEvent == null) {
|
||||||
states.forEach((final String key, final entry) {
|
states.forEach((final String key, final entry) {
|
||||||
if (!entry.containsKey('')) return;
|
if (!entry.containsKey('')) return;
|
||||||
|
@ -1190,7 +1198,7 @@ class Room {
|
||||||
EventUpdate(
|
EventUpdate(
|
||||||
content: content,
|
content: content,
|
||||||
roomID: id,
|
roomID: id,
|
||||||
type: 'state',
|
type: EventUpdateType.state,
|
||||||
eventType: EventTypes.RoomMember,
|
eventType: EventTypes.RoomMember,
|
||||||
sortOrder: 0.0),
|
sortOrder: 0.0),
|
||||||
);
|
);
|
||||||
|
|
|
@ -118,7 +118,6 @@ class Timeline {
|
||||||
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].messageType == MessageTypes.BadEncrypted &&
|
events[i].messageType == MessageTypes.BadEncrypted &&
|
||||||
events[i].content['can_request_session'] == true &&
|
|
||||||
events[i].content['session_id'] == sessionId) {
|
events[i].content['session_id'] == sessionId) {
|
||||||
events[i] = await room.client.encryption
|
events[i] = await room.client.encryption
|
||||||
.decryptRoomEvent(room.id, events[i], store: true);
|
.decryptRoomEvent(room.id, events[i], store: true);
|
||||||
|
@ -211,7 +210,8 @@ class Timeline {
|
||||||
try {
|
try {
|
||||||
if (eventUpdate.roomID != room.id) return;
|
if (eventUpdate.roomID != room.id) return;
|
||||||
|
|
||||||
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
|
if (eventUpdate.type == EventUpdateType.timeline ||
|
||||||
|
eventUpdate.type == EventUpdateType.history) {
|
||||||
var status = eventUpdate.content['status'] ??
|
var status = eventUpdate.content['status'] ??
|
||||||
(eventUpdate.content['unsigned'] is Map<String, dynamic>
|
(eventUpdate.content['unsigned'] is Map<String, dynamic>
|
||||||
? eventUpdate.content['unsigned'][MessageSendingStatusKey]
|
? eventUpdate.content['unsigned'][MessageSendingStatusKey]
|
||||||
|
@ -252,7 +252,7 @@ class Timeline {
|
||||||
var newEvent = Event.fromJson(
|
var newEvent = Event.fromJson(
|
||||||
eventUpdate.content, room, eventUpdate.sortOrder);
|
eventUpdate.content, room, eventUpdate.sortOrder);
|
||||||
|
|
||||||
if (eventUpdate.type == 'history' &&
|
if (eventUpdate.type == EventUpdateType.history &&
|
||||||
events.indexWhere(
|
events.indexWhere(
|
||||||
(e) => e.eventId == eventUpdate.content['event_id']) !=
|
(e) => e.eventId == eventUpdate.content['event_id']) !=
|
||||||
-1) return;
|
-1) return;
|
||||||
|
|
|
@ -20,11 +20,20 @@ import '../../famedlysdk.dart';
|
||||||
import '../../matrix_api.dart';
|
import '../../matrix_api.dart';
|
||||||
import 'logs.dart';
|
import 'logs.dart';
|
||||||
|
|
||||||
|
enum EventUpdateType {
|
||||||
|
timeline,
|
||||||
|
state,
|
||||||
|
history,
|
||||||
|
accountData,
|
||||||
|
ephemeral,
|
||||||
|
inviteState
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a new event (e.g. a message in a room) or an update for an
|
/// Represents a new event (e.g. a message in a room) or an update for an
|
||||||
/// already known event.
|
/// already known event.
|
||||||
class EventUpdate {
|
class EventUpdate {
|
||||||
/// Usually 'timeline', 'state' or whatever.
|
/// Usually 'timeline', 'state' or whatever.
|
||||||
final String type;
|
final EventUpdateType type;
|
||||||
|
|
||||||
/// Most events belong to a room. If not, this equals to eventType.
|
/// Most events belong to a room. If not, this equals to eventType.
|
||||||
final String roomID;
|
final String roomID;
|
||||||
|
|
|
@ -76,12 +76,13 @@ class EmoteSyntax extends InlineSyntax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InlineLatexSyntax extends InlineSyntax {
|
class InlineLatexSyntax extends TagSyntax {
|
||||||
InlineLatexSyntax() : super(r'(?<=\s|^)\$(?=\S)([^\n$]+)(?<=\S)\$(?=\s|$)');
|
InlineLatexSyntax() : super(r'\$', requiresDelimiterRun: true);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool onMatch(InlineParser parser, Match match) {
|
bool onMatchEnd(InlineParser parser, Match match, TagState state) {
|
||||||
final latex = htmlEscape.convert(match[1]);
|
final latex =
|
||||||
|
htmlEscape.convert(parser.source.substring(state.endPos, match.start));
|
||||||
final element = Element('span', [Element.text('code', latex)]);
|
final element = Element('span', [Element.text('code', latex)]);
|
||||||
element.attributes['data-mx-maths'] = latex;
|
element.attributes['data-mx-maths'] = latex;
|
||||||
parser.addNode(element);
|
parser.addNode(element);
|
||||||
|
@ -89,22 +90,28 @@ class InlineLatexSyntax extends InlineSyntax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We also want to allow single-lines of like "$$latex$$"
|
||||||
class BlockLatexSyntax extends BlockSyntax {
|
class BlockLatexSyntax extends BlockSyntax {
|
||||||
@override
|
@override
|
||||||
RegExp get pattern => RegExp(r'^[ ]{0,3}\${2}\s*$');
|
RegExp get pattern => RegExp(r'^[ ]{0,3}\$\$(.*)$');
|
||||||
|
|
||||||
|
final endPattern = RegExp(r'^(.*)\$\$\s*$');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> parseChildLines(BlockParser parser) {
|
List<String> parseChildLines(BlockParser parser) {
|
||||||
var childLines = <String>[];
|
var childLines = <String>[];
|
||||||
parser.advance();
|
var first = true;
|
||||||
while (!parser.isDone) {
|
while (!parser.isDone) {
|
||||||
if (!pattern.hasMatch(parser.current)) {
|
final match = endPattern.firstMatch(parser.current);
|
||||||
|
if (match == null || (first && match.group(1).trim().isEmpty)) {
|
||||||
childLines.add(parser.current);
|
childLines.add(parser.current);
|
||||||
parser.advance();
|
parser.advance();
|
||||||
} else {
|
} else {
|
||||||
|
childLines.add(match.group(1));
|
||||||
parser.advance();
|
parser.advance();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
return childLines;
|
return childLines;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +119,9 @@ class BlockLatexSyntax extends BlockSyntax {
|
||||||
@override
|
@override
|
||||||
Node parse(BlockParser parser) {
|
Node parse(BlockParser parser) {
|
||||||
final childLines = parseChildLines(parser);
|
final childLines = parseChildLines(parser);
|
||||||
final latex = htmlEscape.convert(childLines.join('\n'));
|
// we use .substring(2) as childLines will *always* contain the first two '$$'
|
||||||
|
final latex =
|
||||||
|
htmlEscape.convert(childLines.join('\n').trim().substring(2).trim());
|
||||||
final element = Element('div', [
|
final element = Element('div', [
|
||||||
Element('pre', [Element.text('code', latex)])
|
Element('pre', [Element.text('code', latex)])
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -77,11 +77,11 @@ void main() {
|
||||||
expect(matrix.homeserver, null);
|
expect(matrix.homeserver, null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await matrix.checkServer('https://fakeserver.wrongaddress');
|
await matrix.checkHomeserver('https://fakeserver.wrongaddress');
|
||||||
} on FormatException catch (exception) {
|
} on FormatException catch (exception) {
|
||||||
expect(exception != null, true);
|
expect(exception != null, true);
|
||||||
}
|
}
|
||||||
await matrix.checkServer('https://fakeserver.notexisting');
|
await matrix.checkHomeserver('https://fakeserver.notexisting');
|
||||||
expect(matrix.homeserver.toString(), 'https://fakeserver.notexisting');
|
expect(matrix.homeserver.toString(), 'https://fakeserver.notexisting');
|
||||||
|
|
||||||
final available = await matrix.usernameAvailable('testuser');
|
final available = await matrix.usernameAvailable('testuser');
|
||||||
|
@ -240,55 +240,55 @@ void main() {
|
||||||
|
|
||||||
expect(eventUpdateList[0].eventType, 'm.room.member');
|
expect(eventUpdateList[0].eventType, 'm.room.member');
|
||||||
expect(eventUpdateList[0].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[0].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[0].type, 'state');
|
expect(eventUpdateList[0].type, EventUpdateType.state);
|
||||||
|
|
||||||
expect(eventUpdateList[1].eventType, 'm.room.canonical_alias');
|
expect(eventUpdateList[1].eventType, 'm.room.canonical_alias');
|
||||||
expect(eventUpdateList[1].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[1].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[1].type, 'state');
|
expect(eventUpdateList[1].type, EventUpdateType.state);
|
||||||
|
|
||||||
expect(eventUpdateList[2].eventType, 'm.room.encryption');
|
expect(eventUpdateList[2].eventType, 'm.room.encryption');
|
||||||
expect(eventUpdateList[2].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[2].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[2].type, 'state');
|
expect(eventUpdateList[2].type, EventUpdateType.state);
|
||||||
|
|
||||||
expect(eventUpdateList[3].eventType, 'm.room.pinned_events');
|
expect(eventUpdateList[3].eventType, 'm.room.pinned_events');
|
||||||
expect(eventUpdateList[3].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[3].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[3].type, 'state');
|
expect(eventUpdateList[3].type, EventUpdateType.state);
|
||||||
|
|
||||||
expect(eventUpdateList[4].eventType, 'm.room.member');
|
expect(eventUpdateList[4].eventType, 'm.room.member');
|
||||||
expect(eventUpdateList[4].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[4].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[4].type, 'timeline');
|
expect(eventUpdateList[4].type, EventUpdateType.timeline);
|
||||||
|
|
||||||
expect(eventUpdateList[5].eventType, 'm.room.message');
|
expect(eventUpdateList[5].eventType, 'm.room.message');
|
||||||
expect(eventUpdateList[5].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[5].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[5].type, 'timeline');
|
expect(eventUpdateList[5].type, EventUpdateType.timeline);
|
||||||
|
|
||||||
expect(eventUpdateList[6].eventType, 'm.typing');
|
expect(eventUpdateList[6].eventType, 'm.typing');
|
||||||
expect(eventUpdateList[6].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[6].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[6].type, 'ephemeral');
|
expect(eventUpdateList[6].type, EventUpdateType.ephemeral);
|
||||||
|
|
||||||
expect(eventUpdateList[7].eventType, 'm.receipt');
|
expect(eventUpdateList[7].eventType, 'm.receipt');
|
||||||
expect(eventUpdateList[7].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[7].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[7].type, 'ephemeral');
|
expect(eventUpdateList[7].type, EventUpdateType.ephemeral);
|
||||||
|
|
||||||
expect(eventUpdateList[8].eventType, 'm.receipt');
|
expect(eventUpdateList[8].eventType, 'm.receipt');
|
||||||
expect(eventUpdateList[8].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[8].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[8].type, 'account_data');
|
expect(eventUpdateList[8].type, EventUpdateType.accountData);
|
||||||
|
|
||||||
expect(eventUpdateList[9].eventType, 'm.tag');
|
expect(eventUpdateList[9].eventType, 'm.tag');
|
||||||
expect(eventUpdateList[9].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[9].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[9].type, 'account_data');
|
expect(eventUpdateList[9].type, EventUpdateType.accountData);
|
||||||
|
|
||||||
expect(eventUpdateList[10].eventType, 'org.example.custom.room.config');
|
expect(eventUpdateList[10].eventType, 'org.example.custom.room.config');
|
||||||
expect(eventUpdateList[10].roomID, '!726s6s6q:example.com');
|
expect(eventUpdateList[10].roomID, '!726s6s6q:example.com');
|
||||||
expect(eventUpdateList[10].type, 'account_data');
|
expect(eventUpdateList[10].type, EventUpdateType.accountData);
|
||||||
|
|
||||||
expect(eventUpdateList[11].eventType, 'm.room.name');
|
expect(eventUpdateList[11].eventType, 'm.room.name');
|
||||||
expect(eventUpdateList[11].roomID, '!696r7674:example.com');
|
expect(eventUpdateList[11].roomID, '!696r7674:example.com');
|
||||||
expect(eventUpdateList[11].type, 'invite_state');
|
expect(eventUpdateList[11].type, EventUpdateType.inviteState);
|
||||||
|
|
||||||
expect(eventUpdateList[12].eventType, 'm.room.member');
|
expect(eventUpdateList[12].eventType, 'm.room.member');
|
||||||
expect(eventUpdateList[12].roomID, '!696r7674:example.com');
|
expect(eventUpdateList[12].roomID, '!696r7674:example.com');
|
||||||
expect(eventUpdateList[12].type, 'invite_state');
|
expect(eventUpdateList[12].type, EventUpdateType.inviteState);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('To Device Update Test', () async {
|
test('To Device Update Test', () async {
|
||||||
|
@ -311,12 +311,11 @@ void main() {
|
||||||
|
|
||||||
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||||
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||||
final checkResp =
|
|
||||||
await matrix.checkServer('https://fakeServer.notExisting');
|
await matrix.checkHomeserver('https://fakeServer.notExisting');
|
||||||
|
|
||||||
final loginResp = await matrix.login(user: 'test', password: '1234');
|
final loginResp = await matrix.login(user: 'test', password: '1234');
|
||||||
|
|
||||||
expect(checkResp, true);
|
|
||||||
expect(loginResp != null, true);
|
expect(loginResp != null, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ void main() {
|
||||||
test('setupClient', () async {
|
test('setupClient', () async {
|
||||||
client = await getClient();
|
client = await getClient();
|
||||||
otherClient.database = client.database;
|
otherClient.database = client.database;
|
||||||
await otherClient.checkServer('https://fakeServer.notExisting');
|
await otherClient.checkHomeserver('https://fakeServer.notExisting');
|
||||||
otherClient.connect(
|
otherClient.connect(
|
||||||
newToken: 'abc',
|
newToken: 'abc',
|
||||||
newUserID: '@othertest:fakeServer.notExisting',
|
newUserID: '@othertest:fakeServer.notExisting',
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/utils/logs.dart';
|
import 'package:famedlysdk/src/utils/logs.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
@ -100,7 +102,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
true);
|
true);
|
||||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
true);
|
true);
|
||||||
|
@ -112,17 +114,17 @@ void main() {
|
||||||
|
|
||||||
// rotate after too many messages
|
// rotate after too many messages
|
||||||
sess.sentMessages = 300;
|
sess.sentMessages = 300;
|
||||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
// rotate if devices in room change
|
// rotate if device is blocked
|
||||||
sess =
|
sess =
|
||||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
|
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
|
||||||
.blocked = true;
|
.blocked = true;
|
||||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
false);
|
false);
|
||||||
|
@ -133,16 +135,61 @@ void main() {
|
||||||
sess =
|
sess =
|
||||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
sess.creationTime = DateTime.now().subtract(Duration(days: 30));
|
sess.creationTime = DateTime.now().subtract(Duration(days: 30));
|
||||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
|
// rotate if user leaves
|
||||||
|
sess =
|
||||||
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
|
final room = client.getRoomById(roomId);
|
||||||
|
final member = room.getState('m.room.member', '@alice:example.com');
|
||||||
|
member.content['membership'] = 'leave';
|
||||||
|
room.mJoinedMemberCount--;
|
||||||
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
|
expect(
|
||||||
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
|
false);
|
||||||
|
member.content['membership'] = 'join';
|
||||||
|
room.mJoinedMemberCount++;
|
||||||
|
|
||||||
|
// do not rotate if new device is added
|
||||||
|
sess =
|
||||||
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
|
client.userDeviceKeys['@alice:example.com'].deviceKeys['NEWDEVICE'] =
|
||||||
|
DeviceKeys.fromJson({
|
||||||
|
'user_id': '@alice:example.com',
|
||||||
|
'device_id': 'NEWDEVICE',
|
||||||
|
'algorithms': ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
|
||||||
|
'keys': {
|
||||||
|
'curve25519:JLAFKJWSCS':
|
||||||
|
'3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI',
|
||||||
|
'ed25519:JLAFKJWSCS': 'lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI'
|
||||||
|
},
|
||||||
|
}, client);
|
||||||
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
|
expect(
|
||||||
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
|
true);
|
||||||
|
|
||||||
|
// do not rotate if new user is added
|
||||||
|
member.content['membership'] = 'leave';
|
||||||
|
room.mJoinedMemberCount--;
|
||||||
|
sess =
|
||||||
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
|
member.content['membership'] = 'join';
|
||||||
|
room.mJoinedMemberCount++;
|
||||||
|
await client.encryption.keyManager.clearOrUseOutboundGroupSession(roomId);
|
||||||
|
expect(
|
||||||
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
|
true);
|
||||||
|
|
||||||
// force wipe
|
// force wipe
|
||||||
sess =
|
sess =
|
||||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||||
await client.encryption.keyManager
|
await client.encryption.keyManager
|
||||||
.clearOutboundGroupSession(roomId, wipe: true);
|
.clearOrUseOutboundGroupSession(roomId, wipe: true);
|
||||||
expect(
|
expect(
|
||||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||||
false);
|
false);
|
||||||
|
@ -249,6 +296,30 @@ void main() {
|
||||||
final senderKey = client.identityKey;
|
final senderKey = client.identityKey;
|
||||||
final roomId = '!someroom:example.org';
|
final roomId = '!someroom:example.org';
|
||||||
final sessionId = inbound.session_id();
|
final sessionId = inbound.session_id();
|
||||||
|
final room = Room(id: roomId, client: client);
|
||||||
|
client.rooms.add(room);
|
||||||
|
// we build up an encrypted message so that we can test if it successfully decrypted afterwards
|
||||||
|
room.states['m.room.encrypted'] = Event(
|
||||||
|
senderId: '@test:example.com',
|
||||||
|
type: 'm.room.encrypted',
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: '12345',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
content: {
|
||||||
|
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||||
|
'ciphertext': session.encrypt(json.encode({
|
||||||
|
'type': 'm.room.message',
|
||||||
|
'content': {'msgtype': 'm.text', 'body': 'foxies'},
|
||||||
|
})),
|
||||||
|
'device_id': client.deviceID,
|
||||||
|
'sender_key': client.identityKey,
|
||||||
|
'session_id': sessionId,
|
||||||
|
},
|
||||||
|
stateKey: '',
|
||||||
|
sortOrder: 42.0,
|
||||||
|
);
|
||||||
|
expect(room.lastEvent.type, 'm.room.encrypted');
|
||||||
// set a payload...
|
// set a payload...
|
||||||
var sessionPayload = <String, dynamic>{
|
var sessionPayload = <String, dynamic>{
|
||||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||||
|
@ -379,6 +450,10 @@ void main() {
|
||||||
.length,
|
.length,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
|
// test that it decrypted the last event
|
||||||
|
expect(room.lastEvent.type, 'm.room.message');
|
||||||
|
expect(room.lastEvent.content['body'], 'foxies');
|
||||||
|
|
||||||
inbound.free();
|
inbound.free();
|
||||||
session.free();
|
session.free();
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,7 +54,7 @@ EventUpdate getLastSentEvent(KeyVerification req) {
|
||||||
'sender': req.client.userID,
|
'sender': req.client.userID,
|
||||||
},
|
},
|
||||||
eventType: type,
|
eventType: type,
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: req.room.id,
|
roomID: req.room.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ void main() {
|
||||||
client1 = await getClient();
|
client1 = await getClient();
|
||||||
client2 = Client('othertestclient', httpClient: FakeMatrixApi());
|
client2 = Client('othertestclient', httpClient: FakeMatrixApi());
|
||||||
client2.database = client1.database;
|
client2.database = client1.database;
|
||||||
await client2.checkServer('https://fakeServer.notExisting');
|
await client2.checkHomeserver('https://fakeServer.notExisting');
|
||||||
client2.connect(
|
client2.connect(
|
||||||
newToken: 'abc',
|
newToken: 'abc',
|
||||||
newUserID: '@othertest:fakeServer.notExisting',
|
newUserID: '@othertest:fakeServer.notExisting',
|
||||||
|
@ -446,7 +446,7 @@ void main() {
|
||||||
'sender': client2.userID,
|
'sender': client2.userID,
|
||||||
},
|
},
|
||||||
eventType: 'm.key.verification.ready',
|
eventType: 'm.key.verification.ready',
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: req2.room.id,
|
roomID: req2.room.id,
|
||||||
));
|
));
|
||||||
expect(req2.state, KeyVerificationState.error);
|
expect(req2.state, KeyVerificationState.error);
|
||||||
|
|
|
@ -264,7 +264,7 @@ void main() {
|
||||||
|
|
||||||
test('sendAgain', () async {
|
test('sendAgain', () async {
|
||||||
var matrix = Client('testclient', httpClient: FakeMatrixApi());
|
var matrix = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
await matrix.checkServer('https://fakeServer.notExisting');
|
await matrix.checkHomeserver('https://fakeServer.notExisting');
|
||||||
await matrix.login(user: 'test', password: '1234');
|
await matrix.login(user: 'test', password: '1234');
|
||||||
|
|
||||||
var event = Event.fromJson(
|
var event = Event.fromJson(
|
||||||
|
@ -280,7 +280,7 @@ void main() {
|
||||||
|
|
||||||
test('requestKey', () async {
|
test('requestKey', () async {
|
||||||
var matrix = Client('testclient', httpClient: FakeMatrixApi());
|
var matrix = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
await matrix.checkServer('https://fakeServer.notExisting');
|
await matrix.checkHomeserver('https://fakeServer.notExisting');
|
||||||
await matrix.login(user: 'test', password: '1234');
|
await matrix.login(user: 'test', password: '1234');
|
||||||
|
|
||||||
var event = Event.fromJson(
|
var event = Event.fromJson(
|
||||||
|
@ -990,7 +990,7 @@ void main() {
|
||||||
THUMBNAIL_BUFF,
|
THUMBNAIL_BUFF,
|
||||||
}[url];
|
}[url];
|
||||||
};
|
};
|
||||||
await client.checkServer('https://fakeServer.notExisting');
|
await client.checkHomeserver('https://fakeServer.notExisting');
|
||||||
final room = Room(id: '!localpart:server.abc', client: client);
|
final room = Room(id: '!localpart:server.abc', client: client);
|
||||||
var event = Event.fromJson({
|
var event = Event.fromJson({
|
||||||
'type': EventTypes.Message,
|
'type': EventTypes.Message,
|
||||||
|
@ -1133,7 +1133,7 @@ void main() {
|
||||||
FILE_BUFF,
|
FILE_BUFF,
|
||||||
}[url];
|
}[url];
|
||||||
};
|
};
|
||||||
await client.checkServer('https://fakeServer.notExisting');
|
await client.checkHomeserver('https://fakeServer.notExisting');
|
||||||
final room = Room(id: '!localpart:server.abc', client: await getClient());
|
final room = Room(id: '!localpart:server.abc', client: await getClient());
|
||||||
var event = Event.fromJson({
|
var event = Event.fromJson({
|
||||||
'type': EventTypes.Message,
|
'type': EventTypes.Message,
|
||||||
|
|
|
@ -31,7 +31,7 @@ const pickledOlmAccount =
|
||||||
Future<Client> getClient() async {
|
Future<Client> getClient() async {
|
||||||
final client = Client('testclient', httpClient: FakeMatrixApi());
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
client.database = getDatabase();
|
client.database = getDatabase();
|
||||||
await client.checkServer('https://fakeServer.notExisting');
|
await client.checkHomeserver('https://fakeServer.notExisting');
|
||||||
client.connect(
|
client.connect(
|
||||||
newToken: 'abcd',
|
newToken: 'abcd',
|
||||||
newUserID: '@test:fakeServer.notExisting',
|
newUserID: '@test:fakeServer.notExisting',
|
||||||
|
|
|
@ -73,6 +73,16 @@ void main() {
|
||||||
test('latex', () {
|
test('latex', () {
|
||||||
expect(markdown('meep \$\\frac{2}{3}\$'),
|
expect(markdown('meep \$\\frac{2}{3}\$'),
|
||||||
'meep <span data-mx-maths="\\frac{2}{3}"><code>\\frac{2}{3}</code></span>');
|
'meep <span data-mx-maths="\\frac{2}{3}"><code>\\frac{2}{3}</code></span>');
|
||||||
|
expect(markdown('meep \$hmm *yay*\$'),
|
||||||
|
'meep <span data-mx-maths="hmm *yay*"><code>hmm *yay*</code></span>');
|
||||||
|
expect(markdown('you have \$somevar and \$someothervar'),
|
||||||
|
'you have \$somevar and \$someothervar');
|
||||||
|
expect(markdown('meep ||\$\\frac{2}{3}\$||'),
|
||||||
|
'meep <span data-mx-spoiler=""><span data-mx-maths="\\frac{2}{3}"><code>\\frac{2}{3}</code></span></span>');
|
||||||
|
expect(markdown('meep `\$\\frac{2}{3}\$`'),
|
||||||
|
'meep <code>\$\\frac{2}{3}\$</code>');
|
||||||
|
expect(markdown('hey\n\$\$beep\$\$\nmeow'),
|
||||||
|
'<p>hey</p>\n<div data-mx-maths="beep">\n<pre><code>beep</code></pre>\n</div>\n<p>meow</p>');
|
||||||
expect(markdown('hey\n\$\$\nbeep\nboop\n\$\$\nmeow'),
|
expect(markdown('hey\n\$\$\nbeep\nboop\n\$\$\nmeow'),
|
||||||
'<p>hey</p>\n<div data-mx-maths="beep\nboop">\n<pre><code>beep\nboop</code></pre>\n</div>\n<p>meow</p>');
|
'<p>hey</p>\n<div data-mx-maths="beep\nboop">\n<pre><code>beep\nboop</code></pre>\n</div>\n<p>meow</p>');
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ void main() {
|
||||||
test('storeEventUpdate', () async {
|
test('storeEventUpdate', () async {
|
||||||
// store a simple update
|
// store a simple update
|
||||||
var update = EventUpdate(
|
var update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -58,7 +58,7 @@ void main() {
|
||||||
|
|
||||||
// insert a transaction id
|
// insert a transaction id
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -75,7 +75,7 @@ void main() {
|
||||||
event = await database.getEventById(clientId, 'transaction-1', room);
|
event = await database.getEventById(clientId, 'transaction-1', room);
|
||||||
expect(event.eventId, 'transaction-1');
|
expect(event.eventId, 'transaction-1');
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -98,7 +98,7 @@ void main() {
|
||||||
|
|
||||||
// insert a transaction id if the event id for it already exists
|
// insert a transaction id if the event id for it already exists
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -115,7 +115,7 @@ void main() {
|
||||||
event = await database.getEventById(clientId, '\$event-3', room);
|
event = await database.getEventById(clientId, '\$event-3', room);
|
||||||
expect(event.eventId, '\$event-3');
|
expect(event.eventId, '\$event-3');
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -140,7 +140,7 @@ void main() {
|
||||||
|
|
||||||
// insert transaction id and not update status
|
// insert transaction id and not update status
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -157,7 +157,7 @@ void main() {
|
||||||
event = await database.getEventById(clientId, '\$event-4', room);
|
event = await database.getEventById(clientId, '\$event-4', room);
|
||||||
expect(event.eventId, '\$event-4');
|
expect(event.eventId, '\$event-4');
|
||||||
update = EventUpdate(
|
update = EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
|
|
@ -27,7 +27,7 @@ void main() {
|
||||||
group('MxContent', () {
|
group('MxContent', () {
|
||||||
test('Formatting', () async {
|
test('Formatting', () async {
|
||||||
var client = Client('testclient', httpClient: FakeMatrixApi());
|
var client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
final mxc = 'mxc://exampleserver.abc/abcdefghijklmn';
|
final mxc = 'mxc://exampleserver.abc/abcdefghijklmn';
|
||||||
final content = Uri.parse(mxc);
|
final content = Uri.parse(mxc);
|
||||||
expect(content.isScheme('mxc'), true);
|
expect(content.isScheme('mxc'), true);
|
||||||
|
|
|
@ -179,6 +179,31 @@ void main() {
|
||||||
expect(room.timeCreated, room.lastEvent.originServerTs);
|
expect(room.timeCreated, room.lastEvent.originServerTs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('multiple last event with same sort order', () {
|
||||||
|
room.states['m.room.encrypted'] = Event(
|
||||||
|
senderId: '@test:example.com',
|
||||||
|
type: 'm.room.encrypted',
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: '12345',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
content: {'msgtype': 'm.text', 'body': 'test'},
|
||||||
|
stateKey: '',
|
||||||
|
sortOrder: 42.0);
|
||||||
|
expect(room.lastEvent.type, 'm.room.encrypted');
|
||||||
|
room.states['m.room.messge'] = Event(
|
||||||
|
senderId: '@test:example.com',
|
||||||
|
type: 'm.room.messge',
|
||||||
|
roomId: room.id,
|
||||||
|
room: room,
|
||||||
|
eventId: '12345',
|
||||||
|
originServerTs: DateTime.now(),
|
||||||
|
content: {'msgtype': 'm.text', 'body': 'test'},
|
||||||
|
stateKey: '',
|
||||||
|
sortOrder: 42.0);
|
||||||
|
expect(room.lastEvent.type, 'm.room.encrypted');
|
||||||
|
});
|
||||||
|
|
||||||
test('sendReadReceipt', () async {
|
test('sendReadReceipt', () async {
|
||||||
await room.sendReadReceipt('§1234:fakeServer.notExisting');
|
await room.sendReadReceipt('§1234:fakeServer.notExisting');
|
||||||
});
|
});
|
||||||
|
|
|
@ -49,10 +49,10 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Create', () async {
|
test('Create', () async {
|
||||||
await client.checkServer('https://fakeServer.notExisting');
|
await client.checkHomeserver('https://fakeServer.notExisting');
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -65,7 +65,7 @@ void main() {
|
||||||
},
|
},
|
||||||
sortOrder: room.newSortOrder));
|
sortOrder: room.newSortOrder));
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -114,7 +114,7 @@ void main() {
|
||||||
expect(timeline.events[0].receipts[0].user.id, '@alice:example.com');
|
expect(timeline.events[0].receipts[0].user.id, '@alice:example.com');
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.redaction',
|
eventType: 'm.room.redaction',
|
||||||
content: {
|
content: {
|
||||||
|
@ -149,7 +149,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, 1);
|
||||||
|
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -174,7 +174,7 @@ void main() {
|
||||||
|
|
||||||
test('Send message with error', () async {
|
test('Send message with error', () async {
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -221,7 +221,7 @@ void main() {
|
||||||
test('Resend message', () async {
|
test('Resend message', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -278,7 +278,7 @@ void main() {
|
||||||
test('sort errors on top', () async {
|
test('sort errors on top', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -291,7 +291,7 @@ void main() {
|
||||||
},
|
},
|
||||||
sortOrder: room.newSortOrder));
|
sortOrder: room.newSortOrder));
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -311,7 +311,7 @@ void main() {
|
||||||
test('sending event to failed update', () async {
|
test('sending event to failed update', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -327,7 +327,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, 0);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -347,7 +347,7 @@ void main() {
|
||||||
() async {
|
() async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -363,7 +363,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, 0);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -380,7 +380,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 1);
|
expect(timeline.events[0].status, 1);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -401,7 +401,7 @@ void main() {
|
||||||
() async {
|
() async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -420,7 +420,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, 0);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -439,7 +439,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, 2);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -461,7 +461,7 @@ void main() {
|
||||||
test('sending an event 0 -> -1 -> 2', () async {
|
test('sending an event 0 -> -1 -> 2', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -477,7 +477,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, 0);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -493,7 +493,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, -1);
|
expect(timeline.events[0].status, -1);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -513,7 +513,7 @@ void main() {
|
||||||
test('sending an event 0 -> 2 -> -1', () async {
|
test('sending an event 0 -> 2 -> -1', () async {
|
||||||
timeline.events.clear();
|
timeline.events.clear();
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -529,7 +529,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 0);
|
expect(timeline.events[0].status, 0);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
@ -546,7 +546,7 @@ void main() {
|
||||||
expect(timeline.events[0].status, 2);
|
expect(timeline.events[0].status, 2);
|
||||||
expect(timeline.events.length, 1);
|
expect(timeline.events.length, 1);
|
||||||
client.onEvent.add(EventUpdate(
|
client.onEvent.add(EventUpdate(
|
||||||
type: 'timeline',
|
type: EventUpdateType.timeline,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
eventType: 'm.room.message',
|
eventType: 'm.room.message',
|
||||||
content: {
|
content: {
|
||||||
|
|
|
@ -85,28 +85,28 @@ void main() {
|
||||||
user3.calcDisplayname(mxidLocalPartFallback: false), 'Unknown user');
|
user3.calcDisplayname(mxidLocalPartFallback: false), 'Unknown user');
|
||||||
});
|
});
|
||||||
test('kick', () async {
|
test('kick', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await user1.kick();
|
await user1.kick();
|
||||||
});
|
});
|
||||||
test('ban', () async {
|
test('ban', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await user1.ban();
|
await user1.ban();
|
||||||
});
|
});
|
||||||
test('unban', () async {
|
test('unban', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await user1.unban();
|
await user1.unban();
|
||||||
});
|
});
|
||||||
test('setPower', () async {
|
test('setPower', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await user1.setPower(50);
|
await user1.setPower(50);
|
||||||
});
|
});
|
||||||
test('startDirectChat', () async {
|
test('startDirectChat', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await client.login(user: 'test', password: '1234');
|
await client.login(user: 'test', password: '1234');
|
||||||
await user1.startDirectChat();
|
await user1.startDirectChat();
|
||||||
});
|
});
|
||||||
test('getPresence', () async {
|
test('getPresence', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
await client.handleSync(SyncUpdate.fromJson({
|
await client.handleSync(SyncUpdate.fromJson({
|
||||||
'presence': {
|
'presence': {
|
||||||
'events': [
|
'events': [
|
||||||
|
@ -121,15 +121,15 @@ void main() {
|
||||||
expect(user1.presence.presence.presence, PresenceType.online);
|
expect(user1.presence.presence.presence, PresenceType.online);
|
||||||
});
|
});
|
||||||
test('canBan', () async {
|
test('canBan', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
expect(user1.canBan, false);
|
expect(user1.canBan, false);
|
||||||
});
|
});
|
||||||
test('canKick', () async {
|
test('canKick', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
expect(user1.canKick, false);
|
expect(user1.canKick, false);
|
||||||
});
|
});
|
||||||
test('canChangePowerLevel', () async {
|
test('canChangePowerLevel', () async {
|
||||||
await client.checkServer('https://fakeserver.notexisting');
|
await client.checkHomeserver('https://fakeserver.notexisting');
|
||||||
expect(user1.canChangePowerLevel, false);
|
expect(user1.canChangePowerLevel, false);
|
||||||
});
|
});
|
||||||
test('dispose client', () async {
|
test('dispose client', () async {
|
||||||
|
|
|
@ -24,7 +24,7 @@ void test() async {
|
||||||
Logs.success('++++ Login Alice at ++++');
|
Logs.success('++++ Login Alice at ++++');
|
||||||
testClientA = Client('TestClientA');
|
testClientA = Client('TestClientA');
|
||||||
testClientA.database = getDatabase();
|
testClientA.database = getDatabase();
|
||||||
await testClientA.checkServer(TestUser.homeserver);
|
await testClientA.checkHomeserver(TestUser.homeserver);
|
||||||
await testClientA.login(
|
await testClientA.login(
|
||||||
user: TestUser.username, password: TestUser.password);
|
user: TestUser.username, password: TestUser.password);
|
||||||
assert(testClientA.encryptionEnabled);
|
assert(testClientA.encryptionEnabled);
|
||||||
|
@ -32,7 +32,7 @@ void test() async {
|
||||||
Logs.success('++++ Login Bob ++++');
|
Logs.success('++++ Login Bob ++++');
|
||||||
testClientB = Client('TestClientB');
|
testClientB = Client('TestClientB');
|
||||||
testClientB.database = getDatabase();
|
testClientB.database = getDatabase();
|
||||||
await testClientB.checkServer(TestUser.homeserver);
|
await testClientB.checkHomeserver(TestUser.homeserver);
|
||||||
await testClientB.login(
|
await testClientB.login(
|
||||||
user: TestUser.username2, password: TestUser.password);
|
user: TestUser.username2, password: TestUser.password);
|
||||||
assert(testClientB.encryptionEnabled);
|
assert(testClientB.encryptionEnabled);
|
||||||
|
@ -222,7 +222,7 @@ void test() async {
|
||||||
|
|
||||||
Logs.success('++++ Login Bob in another client ++++');
|
Logs.success('++++ Login Bob in another client ++++');
|
||||||
var testClientC = Client('TestClientC', database: getDatabase());
|
var testClientC = Client('TestClientC', database: getDatabase());
|
||||||
await testClientC.checkServer(TestUser.homeserver);
|
await testClientC.checkHomeserver(TestUser.homeserver);
|
||||||
await testClientC.login(
|
await testClientC.login(
|
||||||
user: TestUser.username2, password: TestUser.password);
|
user: TestUser.username2, password: TestUser.password);
|
||||||
await Future.delayed(Duration(seconds: 3));
|
await Future.delayed(Duration(seconds: 3));
|
||||||
|
|
Loading…
Reference in a new issue