Merge branch 'soru/modularize-e2ee' into soru/cross-signing
This commit is contained in:
commit
d29fb9abfe
|
@ -5,5 +5,7 @@ linter:
|
|||
- camel_case_types
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
todo: ignore
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
23
lib/encryption.dart
Normal file
23
lib/encryption.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
library encryption;
|
||||
|
||||
export './encryption/encryption.dart';
|
||||
export './encryption/key_manager.dart';
|
||||
export './encryption/utils/key_verification.dart';
|
283
lib/encryption/encryption.dart
Normal file
283
lib/encryption/encryption.dart
Normal file
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'key_manager.dart';
|
||||
import 'olm_manager.dart';
|
||||
import 'key_verification_manager.dart';
|
||||
|
||||
class Encryption {
|
||||
final Client client;
|
||||
final bool debug;
|
||||
final bool enableE2eeRecovery;
|
||||
|
||||
bool get enabled => olmManager.enabled;
|
||||
|
||||
/// Returns the base64 encoded keys to store them in a store.
|
||||
/// This String should **never** leave the device!
|
||||
String get pickledOlmAccount => olmManager.pickledOlmAccount;
|
||||
|
||||
String get fingerprintKey => olmManager.fingerprintKey;
|
||||
String get identityKey => olmManager.identityKey;
|
||||
|
||||
KeyManager keyManager;
|
||||
OlmManager olmManager;
|
||||
KeyVerificationManager keyVerificationManager;
|
||||
|
||||
Encryption({
|
||||
this.client,
|
||||
this.debug,
|
||||
this.enableE2eeRecovery,
|
||||
}) {
|
||||
keyManager = KeyManager(this);
|
||||
olmManager = OlmManager(this);
|
||||
keyVerificationManager = KeyVerificationManager(this);
|
||||
}
|
||||
|
||||
Future<void> init(String olmAccount) async {
|
||||
await olmManager.init(olmAccount);
|
||||
}
|
||||
|
||||
void handleDeviceOneTimeKeysCount(Map<String, int> countJson) {
|
||||
olmManager.handleDeviceOneTimeKeysCount(countJson);
|
||||
}
|
||||
|
||||
void onSync() {
|
||||
keyVerificationManager.cleanup();
|
||||
}
|
||||
|
||||
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
|
||||
if (['m.room_key', 'm.room_key_request', 'm.forwarded_room_key']
|
||||
.contains(event.type)) {
|
||||
// a new room key or thelike. We need to handle this asap, before other
|
||||
// events in /sync are handled
|
||||
await keyManager.handleToDeviceEvent(event);
|
||||
}
|
||||
if (event.type.startsWith('m.key.verification.')) {
|
||||
// some key verification event. No need to handle it now, we can easily
|
||||
// do this in the background
|
||||
unawaited(keyVerificationManager.handleToDeviceEvent(event));
|
||||
}
|
||||
}
|
||||
|
||||
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
|
||||
return await olmManager.decryptToDeviceEvent(event);
|
||||
}
|
||||
|
||||
Event decryptRoomEventSync(String roomId, Event event) {
|
||||
if (event.type != EventTypes.Encrypted ||
|
||||
event.content['ciphertext'] == null) return event;
|
||||
Map<String, dynamic> decryptedPayload;
|
||||
try {
|
||||
if (event.content['algorithm'] != 'm.megolm.v1.aes-sha2') {
|
||||
throw (DecryptError.UNKNOWN_ALGORITHM);
|
||||
}
|
||||
final String sessionId = event.content['session_id'];
|
||||
final String senderKey = event.content['sender_key'];
|
||||
final inboundGroupSession =
|
||||
keyManager.getInboundGroupSession(roomId, sessionId, senderKey);
|
||||
if (inboundGroupSession == null) {
|
||||
throw (DecryptError.UNKNOWN_SESSION);
|
||||
}
|
||||
final decryptResult = inboundGroupSession.inboundGroupSession
|
||||
.decrypt(event.content['ciphertext']);
|
||||
final messageIndexKey = event.eventId +
|
||||
event.originServerTs.millisecondsSinceEpoch.toString();
|
||||
var haveIndex = inboundGroupSession.indexes.containsKey(messageIndexKey);
|
||||
if (haveIndex &&
|
||||
inboundGroupSession.indexes[messageIndexKey] !=
|
||||
decryptResult.message_index) {
|
||||
// TODO: maybe clear outbound session, if it is ours
|
||||
throw (DecryptError.CHANNEL_CORRUPTED);
|
||||
}
|
||||
inboundGroupSession.indexes[messageIndexKey] =
|
||||
decryptResult.message_index;
|
||||
if (!haveIndex) {
|
||||
// now we persist the udpated indexes into the database.
|
||||
// the entry should always exist. In the case it doesn't, the following
|
||||
// line *could* throw an error. As that is a future, though, and we call
|
||||
// it un-awaited here, nothing happens, which is exactly the result we want
|
||||
client.database?.updateInboundGroupSessionIndexes(
|
||||
json.encode(inboundGroupSession.indexes),
|
||||
client.id,
|
||||
roomId,
|
||||
sessionId);
|
||||
}
|
||||
decryptedPayload = json.decode(decryptResult.plaintext);
|
||||
} catch (exception) {
|
||||
// alright, if this was actually by our own outbound group session, we might as well clear it
|
||||
if (client.enableE2eeRecovery &&
|
||||
(keyManager
|
||||
.getOutboundGroupSession(roomId)
|
||||
?.outboundGroupSession
|
||||
?.session_id() ??
|
||||
'') ==
|
||||
event.content['session_id']) {
|
||||
keyManager.clearOutboundGroupSession(roomId, wipe: true);
|
||||
}
|
||||
if (exception.toString() == DecryptError.UNKNOWN_SESSION) {
|
||||
decryptedPayload = {
|
||||
'content': event.content,
|
||||
'type': EventTypes.Encrypted,
|
||||
};
|
||||
decryptedPayload['content']['body'] = exception.toString();
|
||||
decryptedPayload['content']['msgtype'] = 'm.bad.encrypted';
|
||||
} else {
|
||||
decryptedPayload = {
|
||||
'content': <String, dynamic>{
|
||||
'msgtype': 'm.bad.encrypted',
|
||||
'body': exception.toString(),
|
||||
},
|
||||
'type': EventTypes.Encrypted,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (event.content['m.relates_to'] != null) {
|
||||
decryptedPayload['content']['m.relates_to'] =
|
||||
event.content['m.relates_to'];
|
||||
}
|
||||
return Event(
|
||||
content: decryptedPayload['content'],
|
||||
type: decryptedPayload['type'],
|
||||
senderId: event.senderId,
|
||||
eventId: event.eventId,
|
||||
roomId: event.roomId,
|
||||
room: event.room,
|
||||
originServerTs: event.originServerTs,
|
||||
unsigned: event.unsigned,
|
||||
stateKey: event.stateKey,
|
||||
prevContent: event.prevContent,
|
||||
status: event.status,
|
||||
sortOrder: event.sortOrder,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Event> decryptRoomEvent(String roomId, Event event,
|
||||
{bool store = false, String updateType = 'timeline'}) async {
|
||||
final doStore = () async {
|
||||
await client.database?.storeEventUpdate(
|
||||
client.id,
|
||||
EventUpdate(
|
||||
eventType: event.type,
|
||||
content: event.toJson(),
|
||||
roomID: event.roomId,
|
||||
type: updateType,
|
||||
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;
|
||||
}
|
||||
|
||||
/// Encrypts the given json payload and creates a send-ready m.room.encrypted
|
||||
/// payload. This will create a new outgoingGroupSession if necessary.
|
||||
Future<Map<String, dynamic>> encryptGroupMessagePayload(
|
||||
String roomId, Map<String, dynamic> payload,
|
||||
{String type = EventTypes.Message}) async {
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null || !room.encrypted || !enabled) {
|
||||
return payload;
|
||||
}
|
||||
if (room.encryptionAlgorithm != 'm.megolm.v1.aes-sha2') {
|
||||
throw ('Unknown encryption algorithm');
|
||||
}
|
||||
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
||||
await keyManager.loadOutboundGroupSession(roomId);
|
||||
}
|
||||
await keyManager.clearOutboundGroupSession(roomId);
|
||||
if (keyManager.getOutboundGroupSession(roomId) == null) {
|
||||
await keyManager.createOutboundGroupSession(roomId);
|
||||
}
|
||||
final sess = keyManager.getOutboundGroupSession(roomId);
|
||||
if (sess == null) {
|
||||
throw ('Unable to create new outbound group session');
|
||||
}
|
||||
final Map<String, dynamic> mRelatesTo = payload.remove('m.relates_to');
|
||||
final payloadContent = {
|
||||
'content': payload,
|
||||
'type': type,
|
||||
'room_id': roomId,
|
||||
};
|
||||
var encryptedPayload = <String, dynamic>{
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'ciphertext':
|
||||
sess.outboundGroupSession.encrypt(json.encode(payloadContent)),
|
||||
'device_id': client.deviceID,
|
||||
'sender_key': identityKey,
|
||||
'session_id': sess.outboundGroupSession.session_id(),
|
||||
if (mRelatesTo != null) 'm.relates_to': mRelatesTo,
|
||||
};
|
||||
sess.sentMessages++;
|
||||
await keyManager.storeOutboundGroupSession(roomId, sess);
|
||||
return encryptedPayload;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
|
||||
DeviceKeys device, String type, Map<String, dynamic> payload) async {
|
||||
return await olmManager.encryptToDeviceMessagePayload(
|
||||
device, type, payload);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> encryptToDeviceMessage(
|
||||
List<DeviceKeys> deviceKeys,
|
||||
String type,
|
||||
Map<String, dynamic> payload) async {
|
||||
return await olmManager.encryptToDeviceMessage(deviceKeys, type, payload);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
keyManager.dispose();
|
||||
olmManager.dispose();
|
||||
keyVerificationManager.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DecryptError {
|
||||
static const String NOT_ENABLED = 'Encryption is not enabled in your client.';
|
||||
static const String UNKNOWN_ALGORITHM = 'Unknown encryption algorithm.';
|
||||
static const String UNKNOWN_SESSION =
|
||||
'The sender has not sent us the session key.';
|
||||
static const String CHANNEL_CORRUPTED =
|
||||
'The secure channel with the sender was corrupted.';
|
||||
}
|
517
lib/encryption/key_manager.dart
Normal file
517
lib/encryption/key_manager.dart
Normal file
|
@ -0,0 +1,517 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import './encryption.dart';
|
||||
import './utils/session_key.dart';
|
||||
import './utils/outbound_group_session.dart';
|
||||
|
||||
class KeyManager {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
final outgoingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
||||
final incomingShareRequests = <String, KeyManagerKeyShareRequest>{};
|
||||
final _inboundGroupSessions = <String, Map<String, SessionKey>>{};
|
||||
final _outboundGroupSessions = <String, OutboundGroupSession>{};
|
||||
final Set<String> _loadedOutboundGroupSessions = <String>{};
|
||||
final Set<String> _requestedSessionIds = <String>{};
|
||||
|
||||
KeyManager(this.encryption);
|
||||
|
||||
/// clear all cached inbound group sessions. useful for testing
|
||||
void clearInboundGroupSessions() {
|
||||
_inboundGroupSessions.clear();
|
||||
}
|
||||
|
||||
void setInboundGroupSession(String roomId, String sessionId, String senderKey,
|
||||
Map<String, dynamic> content,
|
||||
{bool forwarded = false}) {
|
||||
final oldSession =
|
||||
getInboundGroupSession(roomId, sessionId, senderKey, otherRooms: false);
|
||||
if (oldSession != null) {
|
||||
return;
|
||||
}
|
||||
if (content['algorithm'] != 'm.megolm.v1.aes-sha2') {
|
||||
return;
|
||||
}
|
||||
olm.InboundGroupSession inboundGroupSession;
|
||||
try {
|
||||
inboundGroupSession = olm.InboundGroupSession();
|
||||
if (forwarded) {
|
||||
inboundGroupSession.import_session(content['session_key']);
|
||||
} else {
|
||||
inboundGroupSession.create(content['session_key']);
|
||||
}
|
||||
} catch (e) {
|
||||
inboundGroupSession.free();
|
||||
print(
|
||||
'[LibOlm] Could not create new InboundGroupSession: ' + e.toString());
|
||||
return;
|
||||
}
|
||||
if (!_inboundGroupSessions.containsKey(roomId)) {
|
||||
_inboundGroupSessions[roomId] = <String, SessionKey>{};
|
||||
}
|
||||
_inboundGroupSessions[roomId][sessionId] = SessionKey(
|
||||
content: content,
|
||||
inboundGroupSession: inboundGroupSession,
|
||||
indexes: {},
|
||||
key: client.userID,
|
||||
);
|
||||
client.database?.storeInboundGroupSession(
|
||||
client.id,
|
||||
roomId,
|
||||
sessionId,
|
||||
inboundGroupSession.pickle(client.userID),
|
||||
json.encode(content),
|
||||
json.encode({}),
|
||||
);
|
||||
// TODO: somehow try to decrypt last message again
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room != null) {
|
||||
room.onSessionKeyReceived.add(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
SessionKey getInboundGroupSession(
|
||||
String roomId, String sessionId, String senderKey,
|
||||
{bool otherRooms = true}) {
|
||||
if (_inboundGroupSessions.containsKey(roomId) &&
|
||||
_inboundGroupSessions[roomId].containsKey(sessionId)) {
|
||||
return _inboundGroupSessions[roomId][sessionId];
|
||||
}
|
||||
if (!otherRooms) {
|
||||
return null;
|
||||
}
|
||||
// search if this session id is *somehow* found in another room
|
||||
for (final val in _inboundGroupSessions.values) {
|
||||
if (val.containsKey(sessionId)) {
|
||||
return val[sessionId];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Loads an inbound group session
|
||||
Future<SessionKey> loadInboundGroupSession(
|
||||
String roomId, String sessionId, String senderKey) async {
|
||||
if (roomId == null || sessionId == null || senderKey == null) {
|
||||
return null;
|
||||
}
|
||||
if (_inboundGroupSessions.containsKey(roomId) &&
|
||||
_inboundGroupSessions[roomId].containsKey(sessionId)) {
|
||||
return _inboundGroupSessions[roomId][sessionId]; // nothing to do
|
||||
}
|
||||
final session = await client.database
|
||||
?.getDbInboundGroupSession(client.id, roomId, sessionId);
|
||||
if (session == null) {
|
||||
final room = client.getRoomById(roomId);
|
||||
final requestIdent = '$roomId|$sessionId|$senderKey';
|
||||
if (client.enableE2eeRecovery &&
|
||||
room != null &&
|
||||
!_requestedSessionIds.contains(requestIdent)) {
|
||||
// do e2ee recovery
|
||||
_requestedSessionIds.add(requestIdent);
|
||||
unawaited(request(room, sessionId, senderKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!_inboundGroupSessions.containsKey(roomId)) {
|
||||
_inboundGroupSessions[roomId] = <String, SessionKey>{};
|
||||
}
|
||||
final sess = SessionKey.fromDb(session, client.userID);
|
||||
if (!sess.isValid) {
|
||||
return null;
|
||||
}
|
||||
_inboundGroupSessions[roomId][sessionId] = sess;
|
||||
return sess;
|
||||
}
|
||||
|
||||
/// clear all cached inbound group sessions. useful for testing
|
||||
void clearOutboundGroupSessions() {
|
||||
_outboundGroupSessions.clear();
|
||||
}
|
||||
|
||||
/// Clears the existing outboundGroupSession but first checks if the participating
|
||||
/// devices have been changed. Returns false if the session has not been cleared because
|
||||
/// it wasn't necessary.
|
||||
Future<bool> clearOutboundGroupSession(String roomId,
|
||||
{bool wipe = false}) async {
|
||||
final room = client.getRoomById(roomId);
|
||||
final sess = getOutboundGroupSession(roomId);
|
||||
if (room == null || sess == null) {
|
||||
return true;
|
||||
}
|
||||
if (!wipe) {
|
||||
// first check if the devices in the room changed
|
||||
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 maxMessages = encryptionContent != null &&
|
||||
encryptionContent['rotation_period_msgs'] is int
|
||||
? encryptionContent['rotation_period_msgs']
|
||||
: 100;
|
||||
final maxAge = encryptionContent != null &&
|
||||
encryptionContent['rotation_period_ms'] is int
|
||||
? encryptionContent['rotation_period_ms']
|
||||
: 604800000; // default of one week
|
||||
if (sess.sentMessages >= maxMessages ||
|
||||
sess.creationTime
|
||||
.add(Duration(milliseconds: maxAge))
|
||||
.isBefore(DateTime.now())) {
|
||||
wipe = true;
|
||||
}
|
||||
if (!wipe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sess.dispose();
|
||||
_outboundGroupSessions.remove(roomId);
|
||||
await client.database?.removeOutboundGroupSession(client.id, roomId);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> storeOutboundGroupSession(
|
||||
String roomId, OutboundGroupSession sess) async {
|
||||
if (sess == null) {
|
||||
return;
|
||||
}
|
||||
await client.database?.storeOutboundGroupSession(
|
||||
client.id,
|
||||
roomId,
|
||||
sess.outboundGroupSession.pickle(client.userID),
|
||||
json.encode(sess.devices),
|
||||
sess.creationTime,
|
||||
sess.sentMessages);
|
||||
}
|
||||
|
||||
Future<OutboundGroupSession> createOutboundGroupSession(String roomId) async {
|
||||
await clearOutboundGroupSession(roomId, wipe: true);
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
final deviceKeys = await room.getUserDeviceKeys();
|
||||
deviceKeys.removeWhere((k) => k.blocked);
|
||||
final deviceKeyIds = deviceKeys.map((k) => k.deviceId).toList();
|
||||
deviceKeyIds.sort();
|
||||
final outboundGroupSession = olm.OutboundGroupSession();
|
||||
try {
|
||||
outboundGroupSession.create();
|
||||
} catch (e) {
|
||||
outboundGroupSession.free();
|
||||
print('[LibOlm] Unable to create new outboundGroupSession: ' +
|
||||
e.toString());
|
||||
return null;
|
||||
}
|
||||
final rawSession = <String, dynamic>{
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': room.id,
|
||||
'session_id': outboundGroupSession.session_id(),
|
||||
'session_key': outboundGroupSession.session_key(),
|
||||
};
|
||||
setInboundGroupSession(
|
||||
roomId, rawSession['session_id'], encryption.identityKey, rawSession);
|
||||
final sess = OutboundGroupSession(
|
||||
devices: deviceKeyIds,
|
||||
creationTime: DateTime.now(),
|
||||
outboundGroupSession: outboundGroupSession,
|
||||
sentMessages: 0,
|
||||
key: client.userID,
|
||||
);
|
||||
try {
|
||||
await client.sendToDevice(deviceKeys, 'm.room_key', rawSession);
|
||||
await storeOutboundGroupSession(roomId, sess);
|
||||
_outboundGroupSessions[roomId] = sess;
|
||||
} catch (e, s) {
|
||||
print(
|
||||
'[LibOlm] Unable to send the session key to the participating devices: ' +
|
||||
e.toString());
|
||||
print(s);
|
||||
sess.dispose();
|
||||
return null;
|
||||
}
|
||||
return sess;
|
||||
}
|
||||
|
||||
OutboundGroupSession getOutboundGroupSession(String roomId) {
|
||||
return _outboundGroupSessions[roomId];
|
||||
}
|
||||
|
||||
Future<void> loadOutboundGroupSession(String roomId) async {
|
||||
if (_loadedOutboundGroupSessions.contains(roomId) ||
|
||||
_outboundGroupSessions.containsKey(roomId) ||
|
||||
client.database == null) {
|
||||
return; // nothing to do
|
||||
}
|
||||
_loadedOutboundGroupSessions.add(roomId);
|
||||
final session =
|
||||
await client.database.getDbOutboundGroupSession(client.id, roomId);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
final sess = OutboundGroupSession.fromDb(session, client.userID);
|
||||
if (!sess.isValid) {
|
||||
return;
|
||||
}
|
||||
_outboundGroupSessions[roomId] = sess;
|
||||
}
|
||||
|
||||
/// Request a certain key from another device
|
||||
Future<void> request(Room room, String sessionId, String senderKey) async {
|
||||
// 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
|
||||
final devices = await room.getUserDeviceKeys();
|
||||
final requestId = client.generateUniqueTransactionId();
|
||||
final request = KeyManagerKeyShareRequest(
|
||||
requestId: requestId,
|
||||
devices: devices,
|
||||
room: room,
|
||||
sessionId: sessionId,
|
||||
senderKey: senderKey,
|
||||
);
|
||||
await client.sendToDevice(
|
||||
[],
|
||||
'm.room_key_request',
|
||||
{
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': room.id,
|
||||
'sender_key': senderKey,
|
||||
'session_id': sessionId,
|
||||
},
|
||||
'request_id': requestId,
|
||||
'requesting_device_id': client.deviceID,
|
||||
},
|
||||
encrypted: false,
|
||||
toUsers: await room.requestParticipants());
|
||||
outgoingShareRequests[request.requestId] = request;
|
||||
}
|
||||
|
||||
/// Handle an incoming to_device event that is related to key sharing
|
||||
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
|
||||
if (event.type == 'm.room_key_request') {
|
||||
if (!event.content.containsKey('request_id')) {
|
||||
return; // invalid event
|
||||
}
|
||||
if (event.content['action'] == 'request') {
|
||||
// we are *receiving* a request
|
||||
if (!event.content.containsKey('body')) {
|
||||
return; // no body
|
||||
}
|
||||
if (!client.userDeviceKeys.containsKey(event.sender) ||
|
||||
!client.userDeviceKeys[event.sender].deviceKeys
|
||||
.containsKey(event.content['requesting_device_id'])) {
|
||||
return; // device not found
|
||||
}
|
||||
final device = client.userDeviceKeys[event.sender]
|
||||
.deviceKeys[event.content['requesting_device_id']];
|
||||
if (device.userId == client.userID &&
|
||||
device.deviceId == client.deviceID) {
|
||||
return; // ignore requests by ourself
|
||||
}
|
||||
final room = client.getRoomById(event.content['body']['room_id']);
|
||||
if (room == null) {
|
||||
return; // unknown room
|
||||
}
|
||||
final sessionId = event.content['body']['session_id'];
|
||||
final senderKey = event.content['body']['sender_key'];
|
||||
// okay, let's see if we have this session at all
|
||||
if ((await loadInboundGroupSession(room.id, sessionId, senderKey)) ==
|
||||
null) {
|
||||
return; // we don't have this session anyways
|
||||
}
|
||||
final request = KeyManagerKeyShareRequest(
|
||||
requestId: event.content['request_id'],
|
||||
devices: [device],
|
||||
room: room,
|
||||
sessionId: sessionId,
|
||||
senderKey: senderKey,
|
||||
);
|
||||
if (incomingShareRequests.containsKey(request.requestId)) {
|
||||
return; // we don't want to process one and the same request multiple times
|
||||
}
|
||||
incomingShareRequests[request.requestId] = request;
|
||||
final roomKeyRequest =
|
||||
RoomKeyRequest.fromToDeviceEvent(event, this, request);
|
||||
if (device.userId == client.userID &&
|
||||
device.verified &&
|
||||
!device.blocked) {
|
||||
// alright, we can forward the key
|
||||
await roomKeyRequest.forwardKey();
|
||||
} else {
|
||||
client.onRoomKeyRequest
|
||||
.add(roomKeyRequest); // let the client handle this
|
||||
}
|
||||
} else if (event.content['action'] == 'request_cancellation') {
|
||||
// we got told to cancel an incoming request
|
||||
if (!incomingShareRequests.containsKey(event.content['request_id'])) {
|
||||
return; // we don't know this request anyways
|
||||
}
|
||||
// alright, let's just cancel this request
|
||||
final request = incomingShareRequests[event.content['request_id']];
|
||||
request.canceled = true;
|
||||
incomingShareRequests.remove(request.requestId);
|
||||
}
|
||||
} else if (event.type == 'm.forwarded_room_key') {
|
||||
// we *received* an incoming key request
|
||||
if (event.encryptedContent == null) {
|
||||
return; // event wasn't encrypted, this is a security risk
|
||||
}
|
||||
final request = outgoingShareRequests.values.firstWhere(
|
||||
(r) =>
|
||||
r.room.id == event.content['room_id'] &&
|
||||
r.sessionId == event.content['session_id'] &&
|
||||
r.senderKey == event.content['sender_key'],
|
||||
orElse: () => null);
|
||||
if (request == null || request.canceled) {
|
||||
return; // no associated request found or it got canceled
|
||||
}
|
||||
final device = request.devices.firstWhere(
|
||||
(d) =>
|
||||
d.userId == event.sender &&
|
||||
d.curve25519Key == event.encryptedContent['sender_key'],
|
||||
orElse: () => null);
|
||||
if (device == null) {
|
||||
return; // someone we didn't send our request to replied....better ignore this
|
||||
}
|
||||
// TODO: verify that the keys work to decrypt a message
|
||||
// alright, all checks out, let's go ahead and store this session
|
||||
setInboundGroupSession(
|
||||
request.room.id, request.sessionId, request.senderKey, event.content,
|
||||
forwarded: true);
|
||||
request.devices.removeWhere(
|
||||
(k) => k.userId == device.userId && k.deviceId == device.deviceId);
|
||||
outgoingShareRequests.remove(request.requestId);
|
||||
// send cancel to all other devices
|
||||
if (request.devices.isEmpty) {
|
||||
return; // no need to send any cancellation
|
||||
}
|
||||
await client.sendToDevice(
|
||||
request.devices,
|
||||
'm.room_key_request',
|
||||
{
|
||||
'action': 'request_cancellation',
|
||||
'request_id': request.requestId,
|
||||
'requesting_device_id': client.deviceID,
|
||||
},
|
||||
encrypted: false);
|
||||
} else if (event.type == 'm.room_key') {
|
||||
if (event.encryptedContent == null) {
|
||||
return; // the event wasn't encrypted, this is a security risk;
|
||||
}
|
||||
final String roomId = event.content['room_id'];
|
||||
final String sessionId = event.content['session_id'];
|
||||
if (client.userDeviceKeys.containsKey(event.sender) &&
|
||||
client.userDeviceKeys[event.sender].deviceKeys
|
||||
.containsKey(event.content['requesting_device_id'])) {
|
||||
event.content['sender_claimed_ed25519_key'] = client
|
||||
.userDeviceKeys[event.sender]
|
||||
.deviceKeys[event.content['requesting_device_id']]
|
||||
.ed25519Key;
|
||||
}
|
||||
setInboundGroupSession(roomId, sessionId,
|
||||
event.encryptedContent['sender_key'], event.content,
|
||||
forwarded: false);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final sess in _outboundGroupSessions.values) {
|
||||
sess.dispose();
|
||||
}
|
||||
for (final entries in _inboundGroupSessions.values) {
|
||||
for (final sess in entries.values) {
|
||||
sess.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KeyManagerKeyShareRequest {
|
||||
final String requestId;
|
||||
final List<DeviceKeys> devices;
|
||||
final Room room;
|
||||
final String sessionId;
|
||||
final String senderKey;
|
||||
bool canceled;
|
||||
|
||||
KeyManagerKeyShareRequest(
|
||||
{this.requestId,
|
||||
this.devices,
|
||||
this.room,
|
||||
this.sessionId,
|
||||
this.senderKey,
|
||||
this.canceled = false});
|
||||
}
|
||||
|
||||
class RoomKeyRequest extends ToDeviceEvent {
|
||||
KeyManager keyManager;
|
||||
KeyManagerKeyShareRequest request;
|
||||
RoomKeyRequest.fromToDeviceEvent(ToDeviceEvent toDeviceEvent,
|
||||
KeyManager keyManager, KeyManagerKeyShareRequest request) {
|
||||
this.keyManager = keyManager;
|
||||
this.request = request;
|
||||
sender = toDeviceEvent.sender;
|
||||
content = toDeviceEvent.content;
|
||||
type = toDeviceEvent.type;
|
||||
}
|
||||
|
||||
Room get room => request.room;
|
||||
|
||||
DeviceKeys get requestingDevice => request.devices.first;
|
||||
|
||||
Future<void> forwardKey() async {
|
||||
if (request.canceled) {
|
||||
keyManager.incomingShareRequests.remove(request.requestId);
|
||||
return; // request is canceled, don't send anything
|
||||
}
|
||||
var room = this.room;
|
||||
final session = await keyManager.loadInboundGroupSession(
|
||||
room.id, request.sessionId, request.senderKey);
|
||||
var forwardedKeys = <dynamic>[keyManager.encryption.identityKey];
|
||||
for (final key in session.forwardingCurve25519KeyChain) {
|
||||
forwardedKeys.add(key);
|
||||
}
|
||||
await requestingDevice.setVerified(true, keyManager.client);
|
||||
var message = session.content;
|
||||
message['forwarding_curve25519_key_chain'] = forwardedKeys;
|
||||
|
||||
message['session_key'] = session.inboundGroupSession
|
||||
.export_session(session.inboundGroupSession.first_known_index());
|
||||
// send the actual reply of the key back to the requester
|
||||
await keyManager.client.sendToDevice(
|
||||
[requestingDevice],
|
||||
'm.forwarded_room_key',
|
||||
message,
|
||||
);
|
||||
keyManager.incomingShareRequests.remove(request.requestId);
|
||||
}
|
||||
}
|
83
lib/encryption/key_verification_manager.dart
Normal file
83
lib/encryption/key_verification_manager.dart
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import './encryption.dart';
|
||||
import './utils/key_verification.dart';
|
||||
|
||||
class KeyVerificationManager {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
|
||||
KeyVerificationManager(this.encryption);
|
||||
|
||||
final Map<String, KeyVerification> _requests = {};
|
||||
|
||||
Future<void> cleanup() async {
|
||||
for (final entry in _requests.entries) {
|
||||
var dispose = entry.value.canceled ||
|
||||
entry.value.state == KeyVerificationState.done ||
|
||||
entry.value.state == KeyVerificationState.error;
|
||||
if (!dispose) {
|
||||
dispose = !(await entry.value.verifyActivity());
|
||||
}
|
||||
if (dispose) {
|
||||
entry.value.dispose();
|
||||
_requests.remove(entry.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addRequest(KeyVerification request) {
|
||||
if (request.transactionId == null) {
|
||||
return;
|
||||
}
|
||||
_requests[request.transactionId] = request;
|
||||
}
|
||||
|
||||
Future<void> handleToDeviceEvent(ToDeviceEvent event) async {
|
||||
if (!event.type.startsWith('m.key.verification')) {
|
||||
return;
|
||||
}
|
||||
// we have key verification going on!
|
||||
final transactionId = KeyVerification.getTransactionId(event.content);
|
||||
if (transactionId == null) {
|
||||
return; // TODO: send cancel with unknown transaction id
|
||||
}
|
||||
if (_requests.containsKey(transactionId)) {
|
||||
await _requests[transactionId].handlePayload(event.type, event.content);
|
||||
} else {
|
||||
final newKeyRequest =
|
||||
KeyVerification(encryption: encryption, userId: event.sender);
|
||||
await newKeyRequest.handlePayload(event.type, event.content);
|
||||
if (newKeyRequest.state != KeyVerificationState.askAccept) {
|
||||
// okay, something went wrong (unknown transaction id?), just dispose it
|
||||
newKeyRequest.dispose();
|
||||
} else {
|
||||
_requests[transactionId] = newKeyRequest;
|
||||
client.onKeyVerificationRequest.add(newKeyRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final req in _requests.values) {
|
||||
req.dispose();
|
||||
}
|
||||
}
|
||||
}
|
433
lib/encryption/olm_manager.dart
Normal file
433
lib/encryption/olm_manager.dart
Normal file
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import './encryption.dart';
|
||||
|
||||
class OlmManager {
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
olm.Account _olmAccount;
|
||||
|
||||
/// Returns the base64 encoded keys to store them in a store.
|
||||
/// This String should **never** leave the device!
|
||||
String get pickledOlmAccount =>
|
||||
enabled ? _olmAccount.pickle(client.userID) : null;
|
||||
String get fingerprintKey =>
|
||||
enabled ? json.decode(_olmAccount.identity_keys())['ed25519'] : null;
|
||||
String get identityKey =>
|
||||
enabled ? json.decode(_olmAccount.identity_keys())['curve25519'] : null;
|
||||
|
||||
bool get enabled => _olmAccount != null;
|
||||
|
||||
OlmManager(this.encryption);
|
||||
|
||||
/// A map from Curve25519 identity keys to existing olm sessions.
|
||||
Map<String, List<olm.Session>> get olmSessions => _olmSessions;
|
||||
final Map<String, List<olm.Session>> _olmSessions = {};
|
||||
|
||||
Future<void> init(String olmAccount) async {
|
||||
if (olmAccount == null) {
|
||||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
_olmAccount.create();
|
||||
if (await uploadKeys(uploadDeviceKeys: true) == false) {
|
||||
throw ('Upload key failed');
|
||||
}
|
||||
} catch (_) {
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await olm.init();
|
||||
_olmAccount = olm.Account();
|
||||
_olmAccount.unpickle(client.userID, olmAccount);
|
||||
} catch (_) {
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a signature to this json from this olm account.
|
||||
Map<String, dynamic> signJson(Map<String, dynamic> payload) {
|
||||
if (!enabled) throw ('Encryption is disabled');
|
||||
final Map<String, dynamic> unsigned = payload['unsigned'];
|
||||
final Map<String, dynamic> signatures = payload['signatures'];
|
||||
payload.remove('unsigned');
|
||||
payload.remove('signatures');
|
||||
final canonical = canonicalJson.encode(payload);
|
||||
final signature = _olmAccount.sign(String.fromCharCodes(canonical));
|
||||
if (signatures != null) {
|
||||
payload['signatures'] = signatures;
|
||||
} else {
|
||||
payload['signatures'] = <String, dynamic>{};
|
||||
}
|
||||
if (!payload['signatures'].containsKey(client.userID)) {
|
||||
payload['signatures'][client.userID] = <String, dynamic>{};
|
||||
}
|
||||
payload['signatures'][client.userID]['ed25519:${client.deviceID}'] =
|
||||
signature;
|
||||
if (unsigned != null) {
|
||||
payload['unsigned'] = unsigned;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// Checks the signature of a signed json object.
|
||||
bool checkJsonSignature(String key, Map<String, dynamic> signedJson,
|
||||
String userId, String deviceId) {
|
||||
if (!enabled) throw ('Encryption is disabled');
|
||||
final Map<String, dynamic> signatures = signedJson['signatures'];
|
||||
if (signatures == null || !signatures.containsKey(userId)) return false;
|
||||
signedJson.remove('unsigned');
|
||||
signedJson.remove('signatures');
|
||||
if (!signatures[userId].containsKey('ed25519:$deviceId')) return false;
|
||||
final String signature = signatures[userId]['ed25519:$deviceId'];
|
||||
final canonical = canonicalJson.encode(signedJson);
|
||||
final message = String.fromCharCodes(canonical);
|
||||
var isValid = false;
|
||||
final olmutil = olm.Utility();
|
||||
try {
|
||||
olmutil.ed25519_verify(key, message, signature);
|
||||
isValid = true;
|
||||
} catch (e) {
|
||||
isValid = false;
|
||||
print('[LibOlm] Signature check failed: ' + e.toString());
|
||||
} finally {
|
||||
olmutil.free();
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/// Generates new one time keys, signs everything and upload it to the server.
|
||||
Future<bool> uploadKeys(
|
||||
{bool uploadDeviceKeys = false, int oldKeyCount = 0}) async {
|
||||
if (!enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// generate one-time keys
|
||||
// we generate 2/3rds of max, so that other keys people may still have can
|
||||
// still be used
|
||||
final oneTimeKeysCount =
|
||||
(_olmAccount.max_number_of_one_time_keys() * 2 / 3).floor() -
|
||||
oldKeyCount;
|
||||
_olmAccount.generate_one_time_keys(oneTimeKeysCount);
|
||||
final Map<String, dynamic> oneTimeKeys =
|
||||
json.decode(_olmAccount.one_time_keys());
|
||||
|
||||
// now sign all the one-time keys
|
||||
final signedOneTimeKeys = <String, dynamic>{};
|
||||
for (final entry in oneTimeKeys['curve25519'].entries) {
|
||||
final key = entry.key;
|
||||
final value = entry.value;
|
||||
signedOneTimeKeys['signed_curve25519:$key'] = <String, dynamic>{};
|
||||
signedOneTimeKeys['signed_curve25519:$key'] = signJson({
|
||||
'key': value,
|
||||
});
|
||||
}
|
||||
|
||||
// and now generate the payload to upload
|
||||
final keysContent = <String, dynamic>{
|
||||
if (uploadDeviceKeys)
|
||||
'device_keys': {
|
||||
'user_id': client.userID,
|
||||
'device_id': client.deviceID,
|
||||
'algorithms': [
|
||||
'm.olm.v1.curve25519-aes-sha2',
|
||||
'm.megolm.v1.aes-sha2'
|
||||
],
|
||||
'keys': <String, dynamic>{},
|
||||
},
|
||||
};
|
||||
if (uploadDeviceKeys) {
|
||||
final Map<String, dynamic> keys =
|
||||
json.decode(_olmAccount.identity_keys());
|
||||
for (final entry in keys.entries) {
|
||||
final algorithm = entry.key;
|
||||
final value = entry.value;
|
||||
keysContent['device_keys']['keys']['$algorithm:${client.deviceID}'] =
|
||||
value;
|
||||
}
|
||||
keysContent['device_keys'] =
|
||||
signJson(keysContent['device_keys'] as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
final response = await client.api.uploadDeviceKeys(
|
||||
deviceKeys: uploadDeviceKeys
|
||||
? MatrixDeviceKeys.fromJson(keysContent['device_keys'])
|
||||
: null,
|
||||
oneTimeKeys: signedOneTimeKeys,
|
||||
);
|
||||
_olmAccount.mark_keys_as_published();
|
||||
await client.database?.updateClientKeys(pickledOlmAccount, client.id);
|
||||
return response['signed_curve25519'] == oneTimeKeysCount;
|
||||
}
|
||||
|
||||
void handleDeviceOneTimeKeysCount(Map<String, int> countJson) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
// Check if there are at least half of max_number_of_one_time_keys left on the server
|
||||
// and generate and upload more if not.
|
||||
if (countJson.containsKey('signed_curve25519') &&
|
||||
countJson['signed_curve25519'] <
|
||||
(_olmAccount.max_number_of_one_time_keys() / 2)) {
|
||||
uploadKeys(oldKeyCount: countJson['signed_curve25519']);
|
||||
}
|
||||
}
|
||||
|
||||
void storeOlmSession(String curve25519IdentityKey, olm.Session session) {
|
||||
if (client.database == null) {
|
||||
return;
|
||||
}
|
||||
if (!_olmSessions.containsKey(curve25519IdentityKey)) {
|
||||
_olmSessions[curve25519IdentityKey] = [];
|
||||
}
|
||||
final ix = _olmSessions[curve25519IdentityKey]
|
||||
.indexWhere((s) => s.session_id() == session.session_id());
|
||||
if (ix == -1) {
|
||||
// add a new session
|
||||
_olmSessions[curve25519IdentityKey].add(session);
|
||||
} else {
|
||||
// update an existing session
|
||||
_olmSessions[curve25519IdentityKey][ix] = session;
|
||||
}
|
||||
final pickle = session.pickle(client.userID);
|
||||
client.database.storeOlmSession(
|
||||
client.id, curve25519IdentityKey, session.session_id(), pickle);
|
||||
}
|
||||
|
||||
ToDeviceEvent _decryptToDeviceEvent(ToDeviceEvent event) {
|
||||
if (event.type != EventTypes.Encrypted) {
|
||||
return event;
|
||||
}
|
||||
if (event.content['algorithm'] != 'm.olm.v1.curve25519-aes-sha2') {
|
||||
throw ('Unknown algorithm: ${event.content}');
|
||||
}
|
||||
if (!event.content['ciphertext'].containsKey(identityKey)) {
|
||||
throw ("The message isn't sent for this device");
|
||||
}
|
||||
String plaintext;
|
||||
final String senderKey = event.content['sender_key'];
|
||||
final String body = event.content['ciphertext'][identityKey]['body'];
|
||||
final int type = event.content['ciphertext'][identityKey]['type'];
|
||||
if (type != 0 && type != 1) {
|
||||
throw ('Unknown message type');
|
||||
}
|
||||
var existingSessions = olmSessions[senderKey];
|
||||
if (existingSessions != null) {
|
||||
for (var session in existingSessions) {
|
||||
if (type == 0 && session.matches_inbound(body) == true) {
|
||||
plaintext = session.decrypt(type, body);
|
||||
storeOlmSession(senderKey, session);
|
||||
break;
|
||||
} else if (type == 1) {
|
||||
try {
|
||||
plaintext = session.decrypt(type, body);
|
||||
storeOlmSession(senderKey, session);
|
||||
break;
|
||||
} catch (_) {
|
||||
plaintext = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plaintext == null && type != 0) {
|
||||
return event;
|
||||
}
|
||||
|
||||
if (plaintext == null) {
|
||||
var newSession = olm.Session();
|
||||
try {
|
||||
newSession.create_inbound_from(_olmAccount, senderKey, body);
|
||||
_olmAccount.remove_one_time_keys(newSession);
|
||||
client.database?.updateClientKeys(pickledOlmAccount, client.id);
|
||||
plaintext = newSession.decrypt(type, body);
|
||||
storeOlmSession(senderKey, newSession);
|
||||
} catch (_) {
|
||||
newSession?.free();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
final Map<String, dynamic> plainContent = json.decode(plaintext);
|
||||
if (plainContent.containsKey('sender') &&
|
||||
plainContent['sender'] != event.sender) {
|
||||
throw ("Message was decrypted but sender doesn't match");
|
||||
}
|
||||
if (plainContent.containsKey('recipient') &&
|
||||
plainContent['recipient'] != client.userID) {
|
||||
throw ("Message was decrypted but recipient doesn't match");
|
||||
}
|
||||
if (plainContent['recipient_keys'] is Map &&
|
||||
plainContent['recipient_keys']['ed25519'] is String &&
|
||||
plainContent['recipient_keys']['ed25519'] != fingerprintKey) {
|
||||
throw ("Message was decrypted but own fingerprint Key doesn't match");
|
||||
}
|
||||
return ToDeviceEvent(
|
||||
content: plainContent['content'],
|
||||
encryptedContent: event.content,
|
||||
type: plainContent['type'],
|
||||
sender: event.sender,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ToDeviceEvent> decryptToDeviceEvent(ToDeviceEvent event) async {
|
||||
if (event.type != EventTypes.Encrypted) {
|
||||
return event;
|
||||
}
|
||||
final senderKey = event.content['sender_key'];
|
||||
final loadFromDb = () async {
|
||||
if (client.database == null) {
|
||||
return false;
|
||||
}
|
||||
final sessions = await client.database
|
||||
.getSingleOlmSessions(client.id, senderKey, client.userID);
|
||||
if (sessions.isEmpty) {
|
||||
return false; // okay, can't do anything
|
||||
}
|
||||
_olmSessions[senderKey] = sessions;
|
||||
return true;
|
||||
};
|
||||
if (!_olmSessions.containsKey(senderKey)) {
|
||||
await loadFromDb();
|
||||
}
|
||||
event = _decryptToDeviceEvent(event);
|
||||
if (event.type != EventTypes.Encrypted || !(await loadFromDb())) {
|
||||
return event;
|
||||
}
|
||||
// retry to decrypt!
|
||||
return _decryptToDeviceEvent(event);
|
||||
}
|
||||
|
||||
Future<void> startOutgoingOlmSessions(List<DeviceKeys> deviceKeys) async {
|
||||
var requestingKeysFrom = <String, Map<String, String>>{};
|
||||
for (var device in deviceKeys) {
|
||||
if (requestingKeysFrom[device.userId] == null) {
|
||||
requestingKeysFrom[device.userId] = {};
|
||||
}
|
||||
requestingKeysFrom[device.userId][device.deviceId] = 'signed_curve25519';
|
||||
}
|
||||
|
||||
final response =
|
||||
await client.api.requestOneTimeKeys(requestingKeysFrom, timeout: 10000);
|
||||
|
||||
for (var userKeysEntry in response.oneTimeKeys.entries) {
|
||||
final userId = userKeysEntry.key;
|
||||
for (var deviceKeysEntry in userKeysEntry.value.entries) {
|
||||
final deviceId = deviceKeysEntry.key;
|
||||
final fingerprintKey =
|
||||
client.userDeviceKeys[userId].deviceKeys[deviceId].ed25519Key;
|
||||
final identityKey =
|
||||
client.userDeviceKeys[userId].deviceKeys[deviceId].curve25519Key;
|
||||
for (Map<String, dynamic> deviceKey in deviceKeysEntry.value.values) {
|
||||
if (!checkJsonSignature(
|
||||
fingerprintKey, deviceKey, userId, deviceId)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var session = olm.Session();
|
||||
session.create_outbound(_olmAccount, identityKey, deviceKey['key']);
|
||||
await storeOlmSession(identityKey, session);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not create new outbound olm session: ' +
|
||||
e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> encryptToDeviceMessagePayload(
|
||||
DeviceKeys device, String type, Map<String, dynamic> payload) async {
|
||||
var sess = olmSessions[device.curve25519Key];
|
||||
if (sess == null || sess.isEmpty) {
|
||||
final sessions = await client.database
|
||||
.getSingleOlmSessions(client.id, device.curve25519Key, client.userID);
|
||||
if (sessions.isEmpty) {
|
||||
throw ('No olm session found');
|
||||
}
|
||||
sess = _olmSessions[device.curve25519Key] = sessions;
|
||||
}
|
||||
sess.sort((a, b) => a.session_id().compareTo(b.session_id()));
|
||||
final fullPayload = {
|
||||
'type': type,
|
||||
'content': payload,
|
||||
'sender': client.userID,
|
||||
'keys': {'ed25519': fingerprintKey},
|
||||
'recipient': device.userId,
|
||||
'recipient_keys': {'ed25519': device.ed25519Key},
|
||||
};
|
||||
final encryptResult = sess.first.encrypt(json.encode(fullPayload));
|
||||
storeOlmSession(device.curve25519Key, sess.first);
|
||||
final encryptedBody = <String, dynamic>{
|
||||
'algorithm': 'm.olm.v1.curve25519-aes-sha2',
|
||||
'sender_key': identityKey,
|
||||
'ciphertext': <String, dynamic>{},
|
||||
};
|
||||
encryptedBody['ciphertext'][device.curve25519Key] = {
|
||||
'type': encryptResult.type,
|
||||
'body': encryptResult.body,
|
||||
};
|
||||
return encryptedBody;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> encryptToDeviceMessage(
|
||||
List<DeviceKeys> deviceKeys,
|
||||
String type,
|
||||
Map<String, dynamic> payload) async {
|
||||
var data = <String, Map<String, Map<String, dynamic>>>{};
|
||||
final deviceKeysWithoutSession = List<DeviceKeys>.from(deviceKeys);
|
||||
deviceKeysWithoutSession.removeWhere((DeviceKeys deviceKeys) =>
|
||||
olmSessions.containsKey(deviceKeys.curve25519Key));
|
||||
if (deviceKeysWithoutSession.isNotEmpty) {
|
||||
await startOutgoingOlmSessions(deviceKeysWithoutSession);
|
||||
}
|
||||
for (final device in deviceKeys) {
|
||||
if (!data.containsKey(device.userId)) {
|
||||
data[device.userId] = {};
|
||||
}
|
||||
try {
|
||||
data[device.userId][device.deviceId] =
|
||||
await encryptToDeviceMessagePayload(device, type, payload);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Error encrypting to-device event: ' + e.toString());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final sessions in olmSessions.values) {
|
||||
for (final sess in sessions) {
|
||||
sess.free();
|
||||
}
|
||||
}
|
||||
_olmAccount?.free();
|
||||
_olmAccount = null;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,29 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'device_keys_list.dart';
|
||||
import '../client.dart';
|
||||
import '../room.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
|
||||
import '../encryption.dart';
|
||||
|
||||
/*
|
||||
+-------------+ +-----------+
|
||||
|
@ -55,6 +74,9 @@ enum KeyVerificationState {
|
|||
enum KeyVerificationMethod { emoji, numbers }
|
||||
|
||||
List<String> _intersect(List<String> a, List<dynamic> b) {
|
||||
if (b == null || a == null) {
|
||||
return [];
|
||||
}
|
||||
final res = <String>[];
|
||||
for (final v in a) {
|
||||
if (b.contains(v)) {
|
||||
|
@ -94,7 +116,8 @@ _KeyVerificationMethod _makeVerificationMethod(
|
|||
|
||||
class KeyVerification {
|
||||
String transactionId;
|
||||
final Client client;
|
||||
final Encryption encryption;
|
||||
Client get client => encryption.client;
|
||||
final Room room;
|
||||
final String userId;
|
||||
void Function() onUpdate;
|
||||
|
@ -116,7 +139,11 @@ class KeyVerification {
|
|||
String canceledReason;
|
||||
|
||||
KeyVerification(
|
||||
{this.client, this.room, this.userId, String deviceId, this.onUpdate}) {
|
||||
{this.encryption,
|
||||
this.room,
|
||||
this.userId,
|
||||
String deviceId,
|
||||
this.onUpdate}) {
|
||||
lastActivity = DateTime.now();
|
||||
_deviceId ??= deviceId;
|
||||
print('Setting device id constructor: ' + _deviceId.toString());
|
||||
|
@ -489,12 +516,12 @@ class KeyVerification {
|
|||
payload['to'] = userId;
|
||||
payload['body'] =
|
||||
'Attempting verification request. (${type}) Apparently your client doesn\'t support this';
|
||||
type = 'm.room.message';
|
||||
type = EventTypes.Message;
|
||||
}
|
||||
final newTransactionId = await room.sendEvent(payload, type: type);
|
||||
if (transactionId == null) {
|
||||
transactionId = newTransactionId;
|
||||
client.addKeyVerificationRequest(this);
|
||||
encryption.keyVerificationManager.addRequest(this);
|
||||
}
|
||||
} else {
|
||||
await client.sendToDevice(
|
||||
|
@ -514,10 +541,9 @@ class KeyVerification {
|
|||
|
||||
abstract class _KeyVerificationMethod {
|
||||
KeyVerification request;
|
||||
Client client;
|
||||
_KeyVerificationMethod({this.request}) {
|
||||
client = request.client;
|
||||
}
|
||||
Encryption get encryption => request.encryption;
|
||||
Client get client => request.client;
|
||||
_KeyVerificationMethod({this.request});
|
||||
|
||||
Future<void> handlePayload(String type, Map<String, dynamic> payload);
|
||||
bool validateStart(Map<String, dynamic> payload) {
|
||||
|
@ -788,7 +814,7 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod {
|
|||
// we would also add the cross signing key here
|
||||
final deviceKeyId = 'ed25519:${client.deviceID}';
|
||||
mac[deviceKeyId] =
|
||||
_calculateMac(client.fingerprintKey, baseInfo + deviceKeyId);
|
||||
_calculateMac(encryption.fingerprintKey, baseInfo + deviceKeyId);
|
||||
keyList.add(deviceKeyId);
|
||||
|
||||
final masterKey = client.userDeviceKeys.containsKey(client.userID)
|
58
lib/encryption/utils/outbound_group_session.dart
Normal file
58
lib/encryption/utils/outbound_group_session.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import '../../src/database/database.dart' show DbOutboundGroupSession;
|
||||
|
||||
class OutboundGroupSession {
|
||||
List<String> devices;
|
||||
DateTime creationTime;
|
||||
olm.OutboundGroupSession outboundGroupSession;
|
||||
int sentMessages;
|
||||
bool get isValid => outboundGroupSession != null;
|
||||
final String key;
|
||||
|
||||
OutboundGroupSession(
|
||||
{this.devices,
|
||||
this.creationTime,
|
||||
this.outboundGroupSession,
|
||||
this.sentMessages,
|
||||
this.key});
|
||||
|
||||
OutboundGroupSession.fromDb(DbOutboundGroupSession dbEntry, String key)
|
||||
: key = key {
|
||||
outboundGroupSession = olm.OutboundGroupSession();
|
||||
try {
|
||||
outboundGroupSession.unpickle(key, dbEntry.pickle);
|
||||
devices = List<String>.from(json.decode(dbEntry.deviceIds));
|
||||
creationTime = dbEntry.creationTime;
|
||||
sentMessages = dbEntry.sentMessages;
|
||||
} catch (e) {
|
||||
dispose();
|
||||
print(
|
||||
'[LibOlm] Unable to unpickle outboundGroupSession: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
outboundGroupSession?.free();
|
||||
outboundGroupSession = null;
|
||||
}
|
||||
}
|
76
lib/encryption/utils/session_key.dart
Normal file
76
lib/encryption/utils/session_key.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
import '../../src/database/database.dart' show DbInboundGroupSession;
|
||||
|
||||
class SessionKey {
|
||||
Map<String, dynamic> content;
|
||||
Map<String, int> indexes;
|
||||
olm.InboundGroupSession inboundGroupSession;
|
||||
final String key;
|
||||
List<dynamic> get forwardingCurve25519KeyChain =>
|
||||
content['forwarding_curve25519_key_chain'] ?? [];
|
||||
String get senderClaimedEd25519Key =>
|
||||
content['sender_claimed_ed25519_key'] ?? '';
|
||||
String get senderKey => content['sender_key'] ?? '';
|
||||
bool get isValid => inboundGroupSession != null;
|
||||
|
||||
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
|
||||
|
||||
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
|
||||
final parsedContent = Event.getMapFromPayload(dbEntry.content);
|
||||
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
|
||||
content =
|
||||
parsedContent != null ? Map<String, dynamic>.from(parsedContent) : null;
|
||||
indexes = parsedIndexes != null
|
||||
? Map<String, int>.from(parsedIndexes)
|
||||
: <String, int>{};
|
||||
inboundGroupSession = olm.InboundGroupSession();
|
||||
try {
|
||||
inboundGroupSession.unpickle(key, dbEntry.pickle);
|
||||
} catch (e) {
|
||||
dispose();
|
||||
print('[LibOlm] Unable to unpickle inboundGroupSession: ' + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (content != null) {
|
||||
data['content'] = content;
|
||||
}
|
||||
if (indexes != null) {
|
||||
data['indexes'] = indexes;
|
||||
}
|
||||
data['inboundGroupSession'] = inboundGroupSession.pickle(key);
|
||||
return data;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
inboundGroupSession?.free();
|
||||
inboundGroupSession = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => json.encode(toJson());
|
||||
}
|
|
@ -1,55 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
library famedlysdk;
|
||||
|
||||
export 'package:famedlysdk/src/sync/room_update.dart';
|
||||
export 'package:famedlysdk/src/sync/event_update.dart';
|
||||
export 'package:famedlysdk/src/sync/user_update.dart';
|
||||
export 'matrix_api.dart';
|
||||
export 'package:famedlysdk/src/utils/room_update.dart';
|
||||
export 'package:famedlysdk/src/utils/event_update.dart';
|
||||
export 'package:famedlysdk/src/utils/device_keys_list.dart';
|
||||
export 'package:famedlysdk/src/utils/key_verification.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_exception.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_id_string_extension.dart';
|
||||
export 'package:famedlysdk/src/utils/uri_extension.dart';
|
||||
export 'package:famedlysdk/src/utils/matrix_localizations.dart';
|
||||
export 'package:famedlysdk/src/utils/open_id_credentials.dart';
|
||||
export 'package:famedlysdk/src/utils/profile.dart';
|
||||
export 'package:famedlysdk/src/utils/public_rooms_response.dart';
|
||||
export 'package:famedlysdk/src/utils/push_rules.dart';
|
||||
export 'package:famedlysdk/src/utils/receipt.dart';
|
||||
export 'package:famedlysdk/src/utils/states_map.dart';
|
||||
export 'package:famedlysdk/src/utils/to_device_event.dart';
|
||||
export 'package:famedlysdk/src/utils/turn_server_credentials.dart';
|
||||
export 'package:famedlysdk/src/utils/user_device.dart';
|
||||
export 'package:famedlysdk/src/utils/well_known_informations.dart';
|
||||
export 'package:famedlysdk/src/account_data.dart';
|
||||
export 'package:famedlysdk/src/client.dart';
|
||||
export 'package:famedlysdk/src/event.dart';
|
||||
export 'package:famedlysdk/src/key_manager.dart';
|
||||
export 'package:famedlysdk/src/presence.dart';
|
||||
export 'package:famedlysdk/src/room.dart';
|
||||
export 'package:famedlysdk/src/room_account_data.dart';
|
||||
export 'package:famedlysdk/src/timeline.dart';
|
||||
export 'package:famedlysdk/src/user.dart';
|
||||
export 'package:famedlysdk/src/database/database.dart' show Database;
|
||||
|
|
63
lib/matrix_api.dart
Normal file
63
lib/matrix_api.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
library matrix_api;
|
||||
|
||||
export 'package:famedlysdk/matrix_api/matrix_api.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/basic_event_with_sender.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/basic_event.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/device.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/basic_room_event.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/event_context.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/matrix_event.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/event_types.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/events_sync_update.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/filter.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/keys_query_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/login_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/login_types.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/matrix_device_keys.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/matrix_exception.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/message_types.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/presence_content.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/notifications_query_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/one_time_keys_claim_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/open_graph_data.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/open_id_credentials.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/presence.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/profile.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/public_rooms_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/push_rule_set.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/pusher.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/request_token_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/room_alias_informations.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/room_summary.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/server_capabilities.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/stripped_state_event.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/supported_protocol.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/supported_versions.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/sync_update.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/tag.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/third_party_identifier.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/third_party_location.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/third_party_user.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/timeline_history_response.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/turn_server_credentials.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/user_search_result.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/well_known_informations.dart';
|
||||
export 'package:famedlysdk/matrix_api/model/who_is_info.dart';
|
1987
lib/matrix_api/matrix_api.dart
Normal file
1987
lib/matrix_api/matrix_api.dart
Normal file
File diff suppressed because it is too large
Load diff
38
lib/matrix_api/model/basic_event.dart
Normal file
38
lib/matrix_api/model/basic_event.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class BasicEvent {
|
||||
String type;
|
||||
Map<String, dynamic> content;
|
||||
|
||||
BasicEvent({
|
||||
this.type,
|
||||
this.content,
|
||||
});
|
||||
|
||||
BasicEvent.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
content = Map<String, dynamic>.from(json['content']);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['type'] = type;
|
||||
data['content'] = content;
|
||||
return data;
|
||||
}
|
||||
}
|
39
lib/matrix_api/model/basic_event_with_sender.dart
Normal file
39
lib/matrix_api/model/basic_event_with_sender.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'basic_event.dart';
|
||||
|
||||
class BasicEventWithSender extends BasicEvent {
|
||||
String senderId;
|
||||
|
||||
BasicEventWithSender();
|
||||
|
||||
BasicEventWithSender.fromJson(Map<String, dynamic> json) {
|
||||
final basicEvent = BasicEvent.fromJson(json);
|
||||
type = basicEvent.type;
|
||||
content = basicEvent.content;
|
||||
senderId = json['sender'];
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = super.toJson();
|
||||
data['sender'] = senderId;
|
||||
return data;
|
||||
}
|
||||
}
|
46
lib/matrix_api/model/basic_room_event.dart
Normal file
46
lib/matrix_api/model/basic_room_event.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/matrix_api/model/basic_event.dart';
|
||||
|
||||
class BasicRoomEvent extends BasicEvent {
|
||||
String roomId;
|
||||
|
||||
BasicRoomEvent({
|
||||
this.roomId,
|
||||
Map<String, dynamic> content,
|
||||
String type,
|
||||
}) : super(
|
||||
content: content,
|
||||
type: type,
|
||||
);
|
||||
|
||||
BasicRoomEvent.fromJson(Map<String, dynamic> json) {
|
||||
final basicEvent = BasicEvent.fromJson(json);
|
||||
content = basicEvent.content;
|
||||
type = basicEvent.type;
|
||||
roomId = json['room_id'];
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = super.toJson();
|
||||
if (roomId != null) data['room_id'] = roomId;
|
||||
return data;
|
||||
}
|
||||
}
|
46
lib/matrix_api/model/device.dart
Normal file
46
lib/matrix_api/model/device.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Device {
|
||||
String deviceId;
|
||||
String displayName;
|
||||
String lastSeenIp;
|
||||
DateTime lastSeenTs;
|
||||
|
||||
Device.fromJson(Map<String, dynamic> json) {
|
||||
deviceId = json['device_id'];
|
||||
displayName = json['display_name'];
|
||||
lastSeenIp = json['last_seen_ip'];
|
||||
lastSeenTs = DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['device_id'] = deviceId;
|
||||
if (displayName != null) {
|
||||
data['display_name'] = displayName;
|
||||
}
|
||||
if (lastSeenIp != null) {
|
||||
data['last_seen_ip'] = lastSeenIp;
|
||||
}
|
||||
if (lastSeenTs != null) {
|
||||
data['last_seen_ts'] = lastSeenTs.millisecondsSinceEpoch;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
73
lib/matrix_api/model/event_context.dart
Normal file
73
lib/matrix_api/model/event_context.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'matrix_event.dart';
|
||||
|
||||
class EventContext {
|
||||
String end;
|
||||
List<MatrixEvent> eventsAfter;
|
||||
MatrixEvent event;
|
||||
List<MatrixEvent> eventsBefore;
|
||||
String start;
|
||||
List<MatrixEvent> state;
|
||||
|
||||
EventContext.fromJson(Map<String, dynamic> json) {
|
||||
end = json['end'];
|
||||
if (json['events_after'] != null) {
|
||||
eventsAfter = <MatrixEvent>[];
|
||||
json['events_after'].forEach((v) {
|
||||
eventsAfter.add(MatrixEvent.fromJson(v));
|
||||
});
|
||||
}
|
||||
event = json['event'] != null ? MatrixEvent.fromJson(json['event']) : null;
|
||||
if (json['events_before'] != null) {
|
||||
eventsBefore = <MatrixEvent>[];
|
||||
json['events_before'].forEach((v) {
|
||||
eventsBefore.add(MatrixEvent.fromJson(v));
|
||||
});
|
||||
}
|
||||
start = json['start'];
|
||||
if (json['state'] != null) {
|
||||
state = <MatrixEvent>[];
|
||||
json['state'].forEach((v) {
|
||||
state.add(MatrixEvent.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (end != null) {
|
||||
data['end'] = end;
|
||||
}
|
||||
if (eventsAfter != null) {
|
||||
data['events_after'] = eventsAfter.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (event != null) {
|
||||
data['event'] = event.toJson();
|
||||
}
|
||||
if (eventsBefore != null) {
|
||||
data['events_before'] = eventsBefore.map((v) => v.toJson()).toList();
|
||||
}
|
||||
data['start'] = start;
|
||||
if (state != null) {
|
||||
data['state'] = state.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
42
lib/matrix_api/model/event_types.dart
Normal file
42
lib/matrix_api/model/event_types.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
abstract class EventTypes {
|
||||
static const String Message = 'm.room.message';
|
||||
static const String Sticker = 'm.sticker';
|
||||
static const String Redaction = 'm.room.redaction';
|
||||
static const String RoomAliases = 'm.room.aliases';
|
||||
static const String RoomCanonicalAlias = 'm.room.canonical_alias';
|
||||
static const String RoomCreate = 'm.room.create';
|
||||
static const String RoomJoinRules = 'm.room.join_rules';
|
||||
static const String RoomMember = 'm.room.member';
|
||||
static const String RoomPowerLevels = 'm.room.power_levels';
|
||||
static const String RoomName = 'm.room.name';
|
||||
static const String RoomTopic = 'm.room.topic';
|
||||
static const String RoomAvatar = 'm.room.avatar';
|
||||
static const String RoomTombstone = 'm.room.tombsone';
|
||||
static const String GuestAccess = 'm.room.guest_access';
|
||||
static const String HistoryVisibility = 'm.room.history_visibility';
|
||||
static const String Encryption = 'm.room.encryption';
|
||||
static const String Encrypted = 'm.room.encrypted';
|
||||
static const String CallInvite = 'm.room.call.invite';
|
||||
static const String CallAnswer = 'm.room.call.answer';
|
||||
static const String CallCandidates = 'm.room.call.candidates';
|
||||
static const String CallHangup = 'm.room.call.hangup';
|
||||
static const String Unknown = 'm.unknown';
|
||||
}
|
47
lib/matrix_api/model/events_sync_update.dart
Normal file
47
lib/matrix_api/model/events_sync_update.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'matrix_event.dart';
|
||||
|
||||
class EventsSyncUpdate {
|
||||
String start;
|
||||
String end;
|
||||
List<MatrixEvent> chunk;
|
||||
|
||||
EventsSyncUpdate.fromJson(Map<String, dynamic> json) {
|
||||
start = json['start'];
|
||||
end = json['end'];
|
||||
chunk = json['chunk'] != null
|
||||
? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (start != null) {
|
||||
data['start'] = start;
|
||||
}
|
||||
if (end != null) {
|
||||
data['end'] = end;
|
||||
}
|
||||
if (chunk != null) {
|
||||
data['chunk'] = chunk.map((i) => i.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
222
lib/matrix_api/model/filter.dart
Normal file
222
lib/matrix_api/model/filter.dart
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum EventFormat { client, federation }
|
||||
|
||||
class Filter {
|
||||
RoomFilter room;
|
||||
EventFilter presence;
|
||||
EventFilter accountData;
|
||||
EventFormat eventFormat;
|
||||
List<String> eventFields;
|
||||
|
||||
Filter({
|
||||
this.room,
|
||||
this.presence,
|
||||
this.accountData,
|
||||
this.eventFormat,
|
||||
this.eventFields,
|
||||
});
|
||||
|
||||
Filter.fromJson(Map<String, dynamic> json) {
|
||||
room = json['room'] != null ? RoomFilter.fromJson(json['room']) : null;
|
||||
presence = json['presence'] != null
|
||||
? EventFilter.fromJson(json['presence'])
|
||||
: null;
|
||||
accountData = json['account_data'] != null
|
||||
? EventFilter.fromJson(json['account_data'])
|
||||
: null;
|
||||
eventFormat = json['event_format'] != null
|
||||
? EventFormat.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['event_format'])
|
||||
: null;
|
||||
eventFields = json['event_fields'] != null
|
||||
? json['event_fields'].cast<String>()
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (room != null) {
|
||||
data['room'] = room.toJson();
|
||||
}
|
||||
if (presence != null) {
|
||||
data['presence'] = presence.toJson();
|
||||
}
|
||||
if (eventFormat != null) {
|
||||
data['event_format'] = eventFormat.toString().split('.').last;
|
||||
}
|
||||
if (eventFields != null) {
|
||||
data['event_fields'] = eventFields;
|
||||
}
|
||||
if (accountData != null) {
|
||||
data['account_data'] = accountData.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class RoomFilter {
|
||||
List<String> notRooms;
|
||||
List<String> rooms;
|
||||
StateFilter ephemeral;
|
||||
bool includeLeave;
|
||||
StateFilter state;
|
||||
StateFilter timeline;
|
||||
StateFilter accountData;
|
||||
|
||||
RoomFilter({
|
||||
this.notRooms,
|
||||
this.rooms,
|
||||
this.ephemeral,
|
||||
this.includeLeave,
|
||||
this.state,
|
||||
this.timeline,
|
||||
this.accountData,
|
||||
});
|
||||
|
||||
RoomFilter.fromJson(Map<String, dynamic> json) {
|
||||
notRooms = json['not_rooms']?.cast<String>();
|
||||
rooms = json['rooms']?.cast<String>();
|
||||
state = json['state'] != null ? StateFilter.fromJson(json['state']) : null;
|
||||
includeLeave = json['include_leave'];
|
||||
timeline = json['timeline'] != null
|
||||
? StateFilter.fromJson(json['timeline'])
|
||||
: null;
|
||||
ephemeral = json['ephemeral'] != null
|
||||
? StateFilter.fromJson(json['ephemeral'])
|
||||
: null;
|
||||
accountData = json['account_data'] != null
|
||||
? StateFilter.fromJson(json['account_data'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (notRooms != null) {
|
||||
data['not_rooms'] = notRooms;
|
||||
}
|
||||
if (rooms != null) {
|
||||
data['rooms'] = rooms;
|
||||
}
|
||||
if (ephemeral != null) {
|
||||
data['ephemeral'] = ephemeral.toJson();
|
||||
}
|
||||
if (includeLeave != null) {
|
||||
data['include_leave'] = includeLeave;
|
||||
}
|
||||
if (state != null) {
|
||||
data['state'] = state.toJson();
|
||||
}
|
||||
if (timeline != null) {
|
||||
data['timeline'] = timeline.toJson();
|
||||
}
|
||||
if (accountData != null) {
|
||||
data['account_data'] = accountData.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class EventFilter {
|
||||
int limit;
|
||||
List<String> senders;
|
||||
List<String> types;
|
||||
List<String> notRooms;
|
||||
List<String> notSenders;
|
||||
|
||||
EventFilter(
|
||||
{this.limit, this.senders, this.types, this.notRooms, this.notSenders});
|
||||
|
||||
EventFilter.fromJson(Map<String, dynamic> json) {
|
||||
limit = json['limit'];
|
||||
types = json['senders']?.cast<String>();
|
||||
types = json['types']?.cast<String>();
|
||||
notRooms = json['not_rooms']?.cast<String>();
|
||||
notSenders = json['not_senders']?.cast<String>();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (limit != null) data['limit'] = limit;
|
||||
if (types != null) data['types'] = types;
|
||||
if (notRooms != null) data['not_rooms'] = notRooms;
|
||||
if (notSenders != null) data['not_senders'] = notSenders;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class StateFilter extends EventFilter {
|
||||
List<String> notTypes;
|
||||
bool lazyLoadMembers;
|
||||
bool includeRedundantMembers;
|
||||
bool containsUrl;
|
||||
|
||||
StateFilter({
|
||||
this.notTypes,
|
||||
this.lazyLoadMembers,
|
||||
this.includeRedundantMembers,
|
||||
this.containsUrl,
|
||||
int limit,
|
||||
List<String> senders,
|
||||
List<String> types,
|
||||
List<String> notRooms,
|
||||
List<String> notSenders,
|
||||
}) : super(
|
||||
limit: limit,
|
||||
senders: senders,
|
||||
types: types,
|
||||
notRooms: notRooms,
|
||||
notSenders: notSenders,
|
||||
);
|
||||
|
||||
StateFilter.fromJson(Map<String, dynamic> json) {
|
||||
final eventFilter = EventFilter.fromJson(json);
|
||||
limit = eventFilter.limit;
|
||||
senders = eventFilter.senders;
|
||||
types = eventFilter.types;
|
||||
notRooms = eventFilter.notRooms;
|
||||
notSenders = eventFilter.notSenders;
|
||||
|
||||
notTypes = json['not_types']?.cast<String>();
|
||||
lazyLoadMembers = json['lazy_load_members'];
|
||||
includeRedundantMembers = json['include_redundant_members'];
|
||||
containsUrl = json['contains_url'];
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = super.toJson();
|
||||
if (limit != null) {
|
||||
data['limit'] = limit;
|
||||
}
|
||||
if (notTypes != null) {
|
||||
data['not_types'] = notTypes;
|
||||
}
|
||||
if (lazyLoadMembers != null) {
|
||||
data['lazy_load_members'] = notTypes;
|
||||
}
|
||||
if (includeRedundantMembers != null) {
|
||||
data['include_redundant_members'] = notTypes;
|
||||
}
|
||||
if (containsUrl != null) {
|
||||
data['contains_url'] = notTypes;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
62
lib/matrix_api/model/keys_query_response.dart
Normal file
62
lib/matrix_api/model/keys_query_response.dart
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'matrix_device_keys.dart';
|
||||
|
||||
class KeysQueryResponse {
|
||||
Map<String, dynamic> failures;
|
||||
Map<String, Map<String, MatrixDeviceKeys>> deviceKeys;
|
||||
|
||||
KeysQueryResponse.fromJson(Map<String, dynamic> json) {
|
||||
failures = Map<String, dynamic>.from(json['failures']);
|
||||
deviceKeys = json['device_keys'] != null
|
||||
? (json['device_keys'] as Map).map(
|
||||
(k, v) => MapEntry(
|
||||
k,
|
||||
(v as Map).map(
|
||||
(k, v) => MapEntry(
|
||||
k,
|
||||
MatrixDeviceKeys.fromJson(v),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (failures != null) {
|
||||
data['failures'] = failures;
|
||||
}
|
||||
if (deviceKeys != null) {
|
||||
data['device_keys'] = deviceKeys.map(
|
||||
(k, v) => MapEntry(
|
||||
k,
|
||||
v.map(
|
||||
(k, v) => MapEntry(
|
||||
k,
|
||||
v.toJson(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
47
lib/matrix_api/model/login_response.dart
Normal file
47
lib/matrix_api/model/login_response.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'well_known_informations.dart';
|
||||
|
||||
class LoginResponse {
|
||||
String userId;
|
||||
String accessToken;
|
||||
String deviceId;
|
||||
WellKnownInformations wellKnownInformations;
|
||||
|
||||
LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
accessToken = json['access_token'];
|
||||
deviceId = json['device_id'];
|
||||
if (json.containsKey('well_known')) {
|
||||
wellKnownInformations =
|
||||
WellKnownInformations.fromJson(json['well_known']);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (userId != null) data['user_id'] = userId;
|
||||
if (accessToken != null) data['access_token'] = accessToken;
|
||||
if (deviceId != null) data['device_id'] = deviceId;
|
||||
if (wellKnownInformations != null) {
|
||||
data['well_known'] = wellKnownInformations.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
52
lib/matrix_api/model/login_types.dart
Normal file
52
lib/matrix_api/model/login_types.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class LoginTypes {
|
||||
List<Flows> flows;
|
||||
|
||||
LoginTypes.fromJson(Map<String, dynamic> json) {
|
||||
if (json['flows'] != null) {
|
||||
flows = <Flows>[];
|
||||
json['flows'].forEach((v) {
|
||||
flows.add(Flows.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (flows != null) {
|
||||
data['flows'] = flows.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Flows {
|
||||
String type;
|
||||
|
||||
Flows.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['type'] = type;
|
||||
return data;
|
||||
}
|
||||
}
|
70
lib/matrix_api/model/matrix_device_keys.dart
Normal file
70
lib/matrix_api/model/matrix_device_keys.dart
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class MatrixDeviceKeys {
|
||||
String userId;
|
||||
String deviceId;
|
||||
List<String> algorithms;
|
||||
Map<String, String> keys;
|
||||
Map<String, Map<String, String>> signatures;
|
||||
Map<String, dynamic> unsigned;
|
||||
String get deviceDisplayName =>
|
||||
unsigned != null ? unsigned['device_display_name'] : null;
|
||||
|
||||
// This object is used for signing so we need the raw json too
|
||||
Map<String, dynamic> _json;
|
||||
|
||||
MatrixDeviceKeys(
|
||||
this.userId,
|
||||
this.deviceId,
|
||||
this.algorithms,
|
||||
this.keys,
|
||||
this.signatures, {
|
||||
this.unsigned,
|
||||
});
|
||||
|
||||
MatrixDeviceKeys.fromJson(Map<String, dynamic> json) {
|
||||
_json = json;
|
||||
userId = json['user_id'];
|
||||
deviceId = json['device_id'];
|
||||
algorithms = json['algorithms'].cast<String>();
|
||||
keys = Map<String, String>.from(json['keys']);
|
||||
signatures = Map<String, Map<String, String>>.from(
|
||||
(json['signatures'] as Map)
|
||||
.map((k, v) => MapEntry(k, Map<String, String>.from(v))));
|
||||
unsigned = json['unsigned'] != null
|
||||
? Map<String, dynamic>.from(json['unsigned'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = _json ?? <String, dynamic>{};
|
||||
data['user_id'] = userId;
|
||||
data['device_id'] = deviceId;
|
||||
data['algorithms'] = algorithms;
|
||||
data['keys'] = keys;
|
||||
|
||||
if (signatures != null) {
|
||||
data['signatures'] = signatures;
|
||||
}
|
||||
if (unsigned != null) {
|
||||
data['unsigned'] = unsigned;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
72
lib/matrix_api/model/matrix_event.dart
Normal file
72
lib/matrix_api/model/matrix_event.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/matrix_api/model/stripped_state_event.dart';
|
||||
|
||||
class MatrixEvent extends StrippedStateEvent {
|
||||
String eventId;
|
||||
String roomId;
|
||||
DateTime originServerTs;
|
||||
Map<String, dynamic> unsigned;
|
||||
Map<String, dynamic> prevContent;
|
||||
String redacts;
|
||||
|
||||
MatrixEvent();
|
||||
|
||||
MatrixEvent.fromJson(Map<String, dynamic> json) {
|
||||
final strippedStateEvent = StrippedStateEvent.fromJson(json);
|
||||
content = strippedStateEvent.content;
|
||||
type = strippedStateEvent.type;
|
||||
senderId = strippedStateEvent.senderId;
|
||||
stateKey = strippedStateEvent.stateKey;
|
||||
eventId = json['event_id'];
|
||||
roomId = json['room_id'];
|
||||
originServerTs =
|
||||
DateTime.fromMillisecondsSinceEpoch(json['origin_server_ts']);
|
||||
unsigned = json['unsigned'] != null
|
||||
? Map<String, dynamic>.from(json['unsigned'])
|
||||
: null;
|
||||
prevContent = json['prev_content'] != null
|
||||
? Map<String, dynamic>.from(json['prev_content'])
|
||||
: null;
|
||||
redacts = json['redacts'];
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = super.toJson();
|
||||
data['event_id'] = eventId;
|
||||
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
|
||||
if (unsigned != null) {
|
||||
data['unsigned'] = unsigned;
|
||||
}
|
||||
if (prevContent != null) {
|
||||
data['prev_content'] = prevContent;
|
||||
}
|
||||
if (roomId != null) {
|
||||
data['room_id'] = roomId;
|
||||
}
|
||||
if (data['state_key'] == null) {
|
||||
data.remove('state_key');
|
||||
}
|
||||
if (redacts != null) {
|
||||
data['redacts'] = redacts;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,24 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
@ -62,6 +57,7 @@ class MatrixException implements Exception {
|
|||
http.Response response;
|
||||
|
||||
MatrixException(this.response) : raw = json.decode(response.body);
|
||||
MatrixException.fromJson(Map<String, dynamic> content) : raw = content;
|
||||
|
||||
@override
|
||||
String toString() => '$errcode: $errorMessage';
|
||||
|
@ -78,7 +74,9 @@ class MatrixException implements Exception {
|
|||
String get session => raw['session'];
|
||||
|
||||
/// Returns true if the server requires additional authentication.
|
||||
bool get requireAdditionalAuthentication => response.statusCode == 401;
|
||||
bool get requireAdditionalAuthentication => response != null
|
||||
? response.statusCode == 401
|
||||
: authenticationFlows != null;
|
||||
|
||||
/// For each endpoint, a server offers one or more 'flows' that the client can use
|
||||
/// to authenticate itself. Each flow comprises a series of stages. If this request
|
32
lib/matrix_api/model/message_types.dart
Normal file
32
lib/matrix_api/model/message_types.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
abstract class MessageTypes {
|
||||
static const String Text = 'm.text';
|
||||
static const String Emote = 'm.emote';
|
||||
static const String Notice = 'm.notice';
|
||||
static const String Image = 'm.image';
|
||||
static const String Video = 'm.video';
|
||||
static const String Audio = 'm.audio';
|
||||
static const String File = 'm.file';
|
||||
static const String Location = 'm.location';
|
||||
static const String Reply = 'm.relates_to';
|
||||
static const String Sticker = 'm.sticker';
|
||||
static const String BadEncrypted = 'm.bad.encrypted';
|
||||
static const String None = 'm.none';
|
||||
}
|
72
lib/matrix_api/model/notifications_query_response.dart
Normal file
72
lib/matrix_api/model/notifications_query_response.dart
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'matrix_event.dart';
|
||||
|
||||
class NotificationsQueryResponse {
|
||||
String nextToken;
|
||||
List<Notification> notifications;
|
||||
|
||||
NotificationsQueryResponse.fromJson(Map<String, dynamic> json) {
|
||||
nextToken = json['next_token'];
|
||||
notifications = <Notification>[];
|
||||
json['notifications'].forEach((v) {
|
||||
notifications.add(Notification.fromJson(v));
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (nextToken != null) {
|
||||
data['next_token'] = nextToken;
|
||||
}
|
||||
data['notifications'] = notifications.map((v) => v.toJson()).toList();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Notification {
|
||||
List<String> actions;
|
||||
String profileTag;
|
||||
bool read;
|
||||
String roomId;
|
||||
int ts;
|
||||
MatrixEvent event;
|
||||
|
||||
Notification.fromJson(Map<String, dynamic> json) {
|
||||
actions = json['actions'].cast<String>();
|
||||
profileTag = json['profile_tag'];
|
||||
read = json['read'];
|
||||
roomId = json['room_id'];
|
||||
ts = json['ts'];
|
||||
event = MatrixEvent.fromJson(json['event']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['actions'] = actions;
|
||||
if (profileTag != null) {
|
||||
data['profile_tag'] = profileTag;
|
||||
}
|
||||
data['read'] = read;
|
||||
data['room_id'] = roomId;
|
||||
data['ts'] = ts;
|
||||
data['event'] = event.toJson();
|
||||
return data;
|
||||
}
|
||||
}
|
38
lib/matrix_api/model/one_time_keys_claim_response.dart
Normal file
38
lib/matrix_api/model/one_time_keys_claim_response.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class OneTimeKeysClaimResponse {
|
||||
Map<String, dynamic> failures;
|
||||
Map<String, Map<String, dynamic>> oneTimeKeys;
|
||||
|
||||
OneTimeKeysClaimResponse.fromJson(Map<String, dynamic> json) {
|
||||
failures = Map<String, dynamic>.from(json['failures']);
|
||||
oneTimeKeys = Map<String, Map<String, dynamic>>.from(json['one_time_keys']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (failures != null) {
|
||||
data['failures'] = failures;
|
||||
}
|
||||
if (oneTimeKeys != null) {
|
||||
data['one_time_keys'] = oneTimeKeys;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
63
lib/matrix_api/model/open_graph_data.dart
Normal file
63
lib/matrix_api/model/open_graph_data.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class OpenGraphData {
|
||||
String ogTitle;
|
||||
String ogDescription;
|
||||
String ogImage;
|
||||
String ogImageType;
|
||||
int ogImageHeight;
|
||||
int ogImageWidth;
|
||||
int matrixImageSize;
|
||||
|
||||
OpenGraphData.fromJson(Map<String, dynamic> json) {
|
||||
ogTitle = json['og:title'];
|
||||
ogDescription = json['og:description'];
|
||||
ogImage = json['og:image'];
|
||||
ogImageType = json['og:image:type'];
|
||||
ogImageHeight = json['og:image:height'];
|
||||
ogImageWidth = json['og:image:width'];
|
||||
matrixImageSize = json['matrix:image:size'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (ogTitle != null) {
|
||||
data['og:title'] = ogTitle;
|
||||
}
|
||||
if (ogDescription != null) {
|
||||
data['og:description'] = ogDescription;
|
||||
}
|
||||
if (ogImage != null) {
|
||||
data['og:image'] = ogImage;
|
||||
}
|
||||
if (ogImageType != null) {
|
||||
data['og:image:type'] = ogImageType;
|
||||
}
|
||||
if (ogImageHeight != null) {
|
||||
data['og:image:height'] = ogImageHeight;
|
||||
}
|
||||
if (ogImageWidth != null) {
|
||||
data['og:image:width'] = ogImageWidth;
|
||||
}
|
||||
if (matrixImageSize != null) {
|
||||
data['matrix:image:size'] = matrixImageSize;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
40
lib/matrix_api/model/open_id_credentials.dart
Normal file
40
lib/matrix_api/model/open_id_credentials.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class OpenIdCredentials {
|
||||
String accessToken;
|
||||
String tokenType;
|
||||
String matrixServerName;
|
||||
int expiresIn;
|
||||
|
||||
OpenIdCredentials.fromJson(Map<String, dynamic> json) {
|
||||
accessToken = json['access_token'];
|
||||
tokenType = json['token_type'];
|
||||
matrixServerName = json['matrix_server_name'];
|
||||
expiresIn = json['expires_in'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['access_token'] = accessToken;
|
||||
data['token_type'] = tokenType;
|
||||
data['matrix_server_name'] = matrixServerName;
|
||||
data['expires_in'] = expiresIn;
|
||||
return data;
|
||||
}
|
||||
}
|
32
lib/matrix_api/model/presence.dart
Normal file
32
lib/matrix_api/model/presence.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'basic_event_with_sender.dart';
|
||||
import 'presence_content.dart';
|
||||
|
||||
class Presence extends BasicEventWithSender {
|
||||
PresenceContent presence;
|
||||
|
||||
Presence.fromJson(Map<String, dynamic> json) {
|
||||
final basicEvent = BasicEventWithSender.fromJson(json);
|
||||
type = basicEvent.type;
|
||||
content = basicEvent.content;
|
||||
senderId = basicEvent.senderId;
|
||||
presence = PresenceContent.fromJson(content);
|
||||
}
|
||||
}
|
49
lib/matrix_api/model/presence_content.dart
Normal file
49
lib/matrix_api/model/presence_content.dart
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum PresenceType { online, offline, unavailable }
|
||||
|
||||
class PresenceContent {
|
||||
PresenceType presence;
|
||||
int lastActiveAgo;
|
||||
String statusMsg;
|
||||
bool currentlyActive;
|
||||
|
||||
PresenceContent.fromJson(Map<String, dynamic> json) {
|
||||
presence = PresenceType.values
|
||||
.firstWhere((p) => p.toString().split('.').last == json['presence']);
|
||||
lastActiveAgo = json['last_active_ago'];
|
||||
statusMsg = json['status_msg'];
|
||||
currentlyActive = json['currently_active'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['presence'] = presence.toString().split('.').last;
|
||||
if (lastActiveAgo != null) {
|
||||
data['last_active_ago'] = lastActiveAgo;
|
||||
}
|
||||
if (statusMsg != null) {
|
||||
data['status_msg'] = statusMsg;
|
||||
}
|
||||
if (currentlyActive != null) {
|
||||
data['currently_active'] = currentlyActive;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
44
lib/matrix_api/model/profile.dart
Normal file
44
lib/matrix_api/model/profile.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Profile {
|
||||
/// The user's avatar URL if they have set one, otherwise null.
|
||||
Uri avatarUrl;
|
||||
|
||||
/// The user's display name if they have set one, otherwise null.
|
||||
String displayname;
|
||||
|
||||
/// The matrix ID of this user. May be omitted.
|
||||
String userId;
|
||||
|
||||
Map<String, dynamic> additionalContent;
|
||||
|
||||
Profile(this.displayname, this.avatarUrl,
|
||||
{this.additionalContent = const {}});
|
||||
|
||||
Profile.fromJson(Map<String, dynamic> json)
|
||||
: avatarUrl =
|
||||
json['avatar_url'] != null ? Uri.parse(json['avatar_url']) : null,
|
||||
displayname = json['display_name'] ?? json['displayname'],
|
||||
userId = json['user_id'],
|
||||
additionalContent = json;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return additionalContent;
|
||||
}
|
||||
}
|
97
lib/matrix_api/model/public_rooms_response.dart
Normal file
97
lib/matrix_api/model/public_rooms_response.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class PublicRoomsResponse {
|
||||
List<PublicRoom> chunk;
|
||||
String nextBatch;
|
||||
String prevBatch;
|
||||
int totalRoomCountEstimate;
|
||||
|
||||
PublicRoomsResponse.fromJson(Map<String, dynamic> json) {
|
||||
chunk = <PublicRoom>[];
|
||||
json['chunk'].forEach((v) {
|
||||
chunk.add(PublicRoom.fromJson(v));
|
||||
});
|
||||
nextBatch = json['next_batch'];
|
||||
prevBatch = json['prev_batch'];
|
||||
totalRoomCountEstimate = json['total_room_count_estimate'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['chunk'] = chunk.map((v) => v.toJson()).toList();
|
||||
if (nextBatch != null) {
|
||||
data['next_batch'] = nextBatch;
|
||||
}
|
||||
if (prevBatch != null) {
|
||||
data['prev_batch'] = prevBatch;
|
||||
}
|
||||
if (totalRoomCountEstimate != null) {
|
||||
data['total_room_count_estimate'] = totalRoomCountEstimate;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PublicRoom {
|
||||
List<String> aliases;
|
||||
String avatarUrl;
|
||||
bool guestCanJoin;
|
||||
String name;
|
||||
int numJoinedMembers;
|
||||
String roomId;
|
||||
String topic;
|
||||
bool worldReadable;
|
||||
String canonicalAlias;
|
||||
|
||||
PublicRoom.fromJson(Map<String, dynamic> json) {
|
||||
aliases = json['aliases']?.cast<String>();
|
||||
avatarUrl = json['avatar_url'];
|
||||
guestCanJoin = json['guest_can_join'];
|
||||
canonicalAlias = json['canonical_alias'];
|
||||
name = json['name'];
|
||||
numJoinedMembers = json['num_joined_members'];
|
||||
roomId = json['room_id'];
|
||||
topic = json['topic'];
|
||||
worldReadable = json['world_readable'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (aliases != null) {
|
||||
data['aliases'] = aliases;
|
||||
}
|
||||
if (canonicalAlias != null) {
|
||||
data['canonical_alias'] = canonicalAlias;
|
||||
}
|
||||
if (avatarUrl != null) {
|
||||
data['avatar_url'] = avatarUrl;
|
||||
}
|
||||
data['guest_can_join'] = guestCanJoin;
|
||||
if (name != null) {
|
||||
data['name'] = name;
|
||||
}
|
||||
data['num_joined_members'] = numJoinedMembers;
|
||||
data['room_id'] = roomId;
|
||||
if (topic != null) {
|
||||
data['topic'] = topic;
|
||||
}
|
||||
data['world_readable'] = worldReadable;
|
||||
return data;
|
||||
}
|
||||
}
|
143
lib/matrix_api/model/push_rule_set.dart
Normal file
143
lib/matrix_api/model/push_rule_set.dart
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum PushRuleKind { content, override, room, sender, underride }
|
||||
enum PushRuleAction { notify, dont_notify, coalesce, set_tweak }
|
||||
|
||||
class PushRuleSet {
|
||||
List<PushRule> content;
|
||||
List<PushRule> override;
|
||||
List<PushRule> room;
|
||||
List<PushRule> sender;
|
||||
List<PushRule> underride;
|
||||
|
||||
PushRuleSet.fromJson(Map<String, dynamic> json) {
|
||||
if (json['content'] != null) {
|
||||
content =
|
||||
(json['content'] as List).map((i) => PushRule.fromJson(i)).toList();
|
||||
}
|
||||
if (json['override'] != null) {
|
||||
override =
|
||||
(json['override'] as List).map((i) => PushRule.fromJson(i)).toList();
|
||||
}
|
||||
if (json['room'] != null) {
|
||||
room = (json['room'] as List).map((i) => PushRule.fromJson(i)).toList();
|
||||
}
|
||||
if (json['sender'] != null) {
|
||||
sender =
|
||||
(json['sender'] as List).map((i) => PushRule.fromJson(i)).toList();
|
||||
}
|
||||
if (json['underride'] != null) {
|
||||
underride =
|
||||
(json['underride'] as List).map((i) => PushRule.fromJson(i)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (content != null) {
|
||||
data['content'] = content.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (override != null) {
|
||||
data['override'] = override.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (room != null) {
|
||||
data['room'] = room.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (sender != null) {
|
||||
data['sender'] = sender.map((v) => v.toJson()).toList();
|
||||
}
|
||||
if (underride != null) {
|
||||
data['underride'] = underride.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PushRule {
|
||||
List<dynamic> actions;
|
||||
List<PushConditions> conditions;
|
||||
bool isDefault;
|
||||
bool enabled;
|
||||
String pattern;
|
||||
String ruleId;
|
||||
|
||||
PushRule.fromJson(Map<String, dynamic> json) {
|
||||
actions = json['actions'];
|
||||
isDefault = json['default'];
|
||||
enabled = json['enabled'];
|
||||
pattern = json['pattern'];
|
||||
ruleId = json['rule_id'];
|
||||
conditions = json['conditions'] != null
|
||||
? (json['conditions'] as List)
|
||||
.map((i) => PushConditions.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['actions'] = actions;
|
||||
data['default'] = isDefault;
|
||||
data['enabled'] = enabled;
|
||||
if (pattern != null) {
|
||||
data['pattern'] = pattern;
|
||||
}
|
||||
if (conditions != null) {
|
||||
data['conditions'] = conditions.map((i) => i.toJson()).toList();
|
||||
}
|
||||
data['rule_id'] = ruleId;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PushConditions {
|
||||
String key;
|
||||
String kind;
|
||||
String pattern;
|
||||
String isOperator;
|
||||
|
||||
PushConditions(
|
||||
this.kind, {
|
||||
this.key,
|
||||
this.pattern,
|
||||
this.isOperator,
|
||||
});
|
||||
|
||||
PushConditions.fromJson(Map<String, dynamic> json) {
|
||||
key = json['key'];
|
||||
kind = json['kind'];
|
||||
pattern = json['pattern'];
|
||||
isOperator = json['is'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (key != null) {
|
||||
data['key'] = key;
|
||||
}
|
||||
data['kind'] = kind;
|
||||
if (pattern != null) {
|
||||
data['pattern'] = pattern;
|
||||
}
|
||||
if (isOperator != null) {
|
||||
data['is'] = isOperator;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
91
lib/matrix_api/model/pusher.dart
Normal file
91
lib/matrix_api/model/pusher.dart
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Pusher {
|
||||
String pushkey;
|
||||
String kind;
|
||||
String appId;
|
||||
String appDisplayName;
|
||||
String deviceDisplayName;
|
||||
String profileTag;
|
||||
String lang;
|
||||
PusherData data;
|
||||
|
||||
Pusher(
|
||||
this.pushkey,
|
||||
this.appId,
|
||||
this.appDisplayName,
|
||||
this.deviceDisplayName,
|
||||
this.lang,
|
||||
this.data, {
|
||||
this.profileTag,
|
||||
this.kind,
|
||||
});
|
||||
|
||||
Pusher.fromJson(Map<String, dynamic> json) {
|
||||
pushkey = json['pushkey'];
|
||||
kind = json['kind'];
|
||||
appId = json['app_id'];
|
||||
appDisplayName = json['app_display_name'];
|
||||
deviceDisplayName = json['device_display_name'];
|
||||
profileTag = json['profile_tag'];
|
||||
lang = json['lang'];
|
||||
data = PusherData.fromJson(json['data']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['pushkey'] = pushkey;
|
||||
data['kind'] = kind;
|
||||
data['app_id'] = appId;
|
||||
data['app_display_name'] = appDisplayName;
|
||||
data['device_display_name'] = deviceDisplayName;
|
||||
if (profileTag != null) {
|
||||
data['profile_tag'] = profileTag;
|
||||
}
|
||||
data['lang'] = lang;
|
||||
data['data'] = this.data.toJson();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PusherData {
|
||||
Uri url;
|
||||
String format;
|
||||
|
||||
PusherData({
|
||||
this.url,
|
||||
this.format,
|
||||
});
|
||||
|
||||
PusherData.fromJson(Map<String, dynamic> json) {
|
||||
url = Uri.parse(json['url']);
|
||||
format = json['format'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (url != null) {
|
||||
data['url'] = url.toString();
|
||||
}
|
||||
if (format != null) {
|
||||
data['format'] = format;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
34
lib/matrix_api/model/request_token_response.dart
Normal file
34
lib/matrix_api/model/request_token_response.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class RequestTokenResponse {
|
||||
String sid;
|
||||
String submitUrl;
|
||||
|
||||
RequestTokenResponse.fromJson(Map<String, dynamic> json) {
|
||||
sid = json['sid'];
|
||||
submitUrl = json['submit_url'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['sid'] = sid;
|
||||
data['submit_url'] = submitUrl;
|
||||
return data;
|
||||
}
|
||||
}
|
34
lib/matrix_api/model/room_alias_informations.dart
Normal file
34
lib/matrix_api/model/room_alias_informations.dart
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class RoomAliasInformations {
|
||||
String roomId;
|
||||
List<String> servers;
|
||||
|
||||
RoomAliasInformations.fromJson(Map<String, dynamic> json) {
|
||||
roomId = json['room_id'];
|
||||
servers = json['servers'].cast<String>();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['room_id'] = roomId;
|
||||
data['servers'] = servers;
|
||||
return data;
|
||||
}
|
||||
}
|
42
lib/matrix_api/model/room_summary.dart
Normal file
42
lib/matrix_api/model/room_summary.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class RoomSummary {
|
||||
List<String> mHeroes;
|
||||
int mJoinedMemberCount;
|
||||
int mInvitedMemberCount;
|
||||
RoomSummary.fromJson(Map<String, dynamic> json) {
|
||||
mHeroes =
|
||||
json['m.heroes'] != null ? List<String>.from(json['m.heroes']) : null;
|
||||
mJoinedMemberCount = json['m.joined_member_count'];
|
||||
mInvitedMemberCount = json['m.invited_member_count'];
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (mHeroes != null) {
|
||||
data['m.heroes'] = mHeroes;
|
||||
}
|
||||
if (mJoinedMemberCount != null) {
|
||||
data['m.joined_member_count'] = mJoinedMemberCount;
|
||||
}
|
||||
if (mInvitedMemberCount != null) {
|
||||
data['m.invited_member_count'] = mInvitedMemberCount;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
89
lib/matrix_api/model/server_capabilities.dart
Normal file
89
lib/matrix_api/model/server_capabilities.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum RoomVersionStability { stable, unstable }
|
||||
|
||||
class ServerCapabilities {
|
||||
MChangePassword mChangePassword;
|
||||
MRoomVersions mRoomVersions;
|
||||
Map<String, dynamic> customCapabilities;
|
||||
|
||||
ServerCapabilities.fromJson(Map<String, dynamic> json) {
|
||||
mChangePassword = json['m.change_password'] != null
|
||||
? MChangePassword.fromJson(json['m.change_password'])
|
||||
: null;
|
||||
mRoomVersions = json['m.room_versions'] != null
|
||||
? MRoomVersions.fromJson(json['m.room_versions'])
|
||||
: null;
|
||||
customCapabilities = Map<String, dynamic>.from(json);
|
||||
customCapabilities.remove('m.change_password');
|
||||
customCapabilities.remove('m.room_versions');
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (mChangePassword != null) {
|
||||
data['m.change_password'] = mChangePassword.toJson();
|
||||
}
|
||||
if (mRoomVersions != null) {
|
||||
data['m.room_versions'] = mRoomVersions.toJson();
|
||||
}
|
||||
for (final entry in customCapabilities.entries) {
|
||||
data[entry.key] = entry.value;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class MChangePassword {
|
||||
bool enabled;
|
||||
|
||||
MChangePassword.fromJson(Map<String, dynamic> json) {
|
||||
enabled = json['enabled'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['enabled'] = enabled;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class MRoomVersions {
|
||||
String defaultVersion;
|
||||
Map<String, RoomVersionStability> available;
|
||||
|
||||
MRoomVersions.fromJson(Map<String, dynamic> json) {
|
||||
defaultVersion = json['default'];
|
||||
available = (json['available'] as Map).map<String, RoomVersionStability>(
|
||||
(k, v) => MapEntry(
|
||||
k,
|
||||
RoomVersionStability.values
|
||||
.firstWhere((r) => r.toString().split('.').last == v),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['default'] = defaultVersion;
|
||||
data['available'] = available.map<String, dynamic>(
|
||||
(k, v) => MapEntry(k, v.toString().split('.').last));
|
||||
return data;
|
||||
}
|
||||
}
|
39
lib/matrix_api/model/stripped_state_event.dart
Normal file
39
lib/matrix_api/model/stripped_state_event.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/matrix_api/model/basic_event_with_sender.dart';
|
||||
|
||||
class StrippedStateEvent extends BasicEventWithSender {
|
||||
String stateKey;
|
||||
|
||||
StrippedStateEvent();
|
||||
StrippedStateEvent.fromJson(Map<String, dynamic> json) {
|
||||
final basicEvent = BasicEventWithSender.fromJson(json);
|
||||
content = basicEvent.content;
|
||||
type = basicEvent.type;
|
||||
senderId = basicEvent.senderId;
|
||||
stateKey = json['state_key'];
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = super.toJson();
|
||||
data['state_key'] = stateKey;
|
||||
return data;
|
||||
}
|
||||
}
|
92
lib/matrix_api/model/supported_protocol.dart
Normal file
92
lib/matrix_api/model/supported_protocol.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class SupportedProtocol {
|
||||
List<String> userFields;
|
||||
List<String> locationFields;
|
||||
String icon;
|
||||
Map<String, ProtocolFieldType> fieldTypes;
|
||||
List<ProtocolInstance> instances;
|
||||
|
||||
SupportedProtocol.fromJson(Map<String, dynamic> json) {
|
||||
userFields = json['user_fields'].cast<String>();
|
||||
locationFields = json['location_fields'].cast<String>();
|
||||
icon = json['icon'];
|
||||
fieldTypes = (json['field_types'] as Map)
|
||||
.map((k, v) => MapEntry(k, ProtocolFieldType.fromJson(v)));
|
||||
instances = <ProtocolInstance>[];
|
||||
json['instances'].forEach((v) {
|
||||
instances.add(ProtocolInstance.fromJson(v));
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['user_fields'] = userFields;
|
||||
data['location_fields'] = locationFields;
|
||||
data['icon'] = icon;
|
||||
data['field_types'] = fieldTypes.map((k, v) => MapEntry(k, v.toJson()));
|
||||
|
||||
data['instances'] = instances.map((v) => v.toJson()).toList();
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class ProtocolFieldType {
|
||||
String regexp;
|
||||
String placeholder;
|
||||
|
||||
ProtocolFieldType.fromJson(Map<String, dynamic> json) {
|
||||
regexp = json['regexp'];
|
||||
placeholder = json['placeholder'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['regexp'] = regexp;
|
||||
data['placeholder'] = placeholder;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class ProtocolInstance {
|
||||
String networkId;
|
||||
String desc;
|
||||
String icon;
|
||||
dynamic fields;
|
||||
|
||||
ProtocolInstance.fromJson(Map<String, dynamic> json) {
|
||||
networkId = json['network_id'];
|
||||
desc = json['desc'];
|
||||
icon = json['icon'];
|
||||
fields = json['fields'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['network_id'] = networkId;
|
||||
data['desc'] = desc;
|
||||
if (icon != null) {
|
||||
data['icon'] = icon;
|
||||
}
|
||||
data['fields'] = fields;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
36
lib/matrix_api/model/supported_versions.dart
Normal file
36
lib/matrix_api/model/supported_versions.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class SupportedVersions {
|
||||
List<String> versions;
|
||||
Map<String, bool> unstableFeatures;
|
||||
|
||||
SupportedVersions.fromJson(Map<String, dynamic> json) {
|
||||
versions = json['versions'].cast<String>();
|
||||
unstableFeatures = Map<String, bool>.from(json['unstable_features']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['versions'] = versions;
|
||||
if (unstableFeatures != null) {
|
||||
data['unstable_features'] = unstableFeatures;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
321
lib/matrix_api/model/sync_update.dart
Normal file
321
lib/matrix_api/model/sync_update.dart
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'basic_event_with_sender.dart';
|
||||
import 'basic_room_event.dart';
|
||||
import 'stripped_state_event.dart';
|
||||
import 'matrix_event.dart';
|
||||
import 'basic_event.dart';
|
||||
import 'presence.dart';
|
||||
import 'room_summary.dart';
|
||||
|
||||
class SyncUpdate {
|
||||
String nextBatch;
|
||||
RoomsUpdate rooms;
|
||||
List<Presence> presence;
|
||||
List<BasicEvent> accountData;
|
||||
List<BasicEventWithSender> toDevice;
|
||||
DeviceListsUpdate deviceLists;
|
||||
Map<String, int> deviceOneTimeKeysCount;
|
||||
|
||||
SyncUpdate.fromJson(Map<String, dynamic> json) {
|
||||
nextBatch = json['next_batch'];
|
||||
rooms = json['rooms'] != null ? RoomsUpdate.fromJson(json['rooms']) : null;
|
||||
presence = (json['presence'] != null && json['presence']['events'] != null)
|
||||
? (json['presence']['events'] as List)
|
||||
.map((i) => Presence.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
accountData =
|
||||
(json['account_data'] != null && json['account_data']['events'] != null)
|
||||
? (json['account_data']['events'] as List)
|
||||
.map((i) => BasicEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
toDevice =
|
||||
(json['to_device'] != null && json['to_device']['events'] != null)
|
||||
? (json['to_device']['events'] as List)
|
||||
.map((i) => BasicEventWithSender.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
deviceLists = json['device_lists'] != null
|
||||
? DeviceListsUpdate.fromJson(json['device_lists'])
|
||||
: null;
|
||||
deviceOneTimeKeysCount = json['device_one_time_keys_count'] != null
|
||||
? Map<String, int>.from(json['device_one_time_keys_count'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['next_batch'] = nextBatch;
|
||||
if (rooms != null) {
|
||||
data['rooms'] = rooms.toJson();
|
||||
}
|
||||
if (presence != null) {
|
||||
data['presence'] = {
|
||||
'events': presence.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (accountData != null) {
|
||||
data['account_data'] = {
|
||||
'events': accountData.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (toDevice != null) {
|
||||
data['to_device'] = {
|
||||
'events': toDevice.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (deviceLists != null) {
|
||||
data['device_lists'] = deviceLists.toJson();
|
||||
}
|
||||
if (deviceOneTimeKeysCount != null) {
|
||||
data['device_one_time_keys_count'] = deviceOneTimeKeysCount;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class RoomsUpdate {
|
||||
Map<String, JoinedRoomUpdate> join;
|
||||
Map<String, InvitedRoomUpdate> invite;
|
||||
Map<String, LeftRoomUpdate> leave;
|
||||
|
||||
RoomsUpdate.fromJson(Map<String, dynamic> json) {
|
||||
join = json['join'] != null
|
||||
? (json['join'] as Map)
|
||||
.map((k, v) => MapEntry(k, JoinedRoomUpdate.fromJson(v)))
|
||||
: null;
|
||||
invite = json['invite'] != null
|
||||
? (json['invite'] as Map)
|
||||
.map((k, v) => MapEntry(k, InvitedRoomUpdate.fromJson(v)))
|
||||
: null;
|
||||
leave = json['leave'] != null
|
||||
? (json['leave'] as Map)
|
||||
.map((k, v) => MapEntry(k, LeftRoomUpdate.fromJson(v)))
|
||||
: null;
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (join != null) {
|
||||
data['join'] = join.map((k, v) => MapEntry(k, v.toJson()));
|
||||
}
|
||||
if (invite != null) {
|
||||
data['invite'] = invite.map((k, v) => MapEntry(k, v.toJson()));
|
||||
}
|
||||
if (leave != null) {
|
||||
data['leave'] = leave.map((k, v) => MapEntry(k, v.toJson()));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SyncRoomUpdate {}
|
||||
|
||||
class JoinedRoomUpdate extends SyncRoomUpdate {
|
||||
RoomSummary summary;
|
||||
List<MatrixEvent> state;
|
||||
TimelineUpdate timeline;
|
||||
List<BasicRoomEvent> ephemeral;
|
||||
List<BasicRoomEvent> accountData;
|
||||
UnreadNotificationCounts unreadNotifications;
|
||||
|
||||
JoinedRoomUpdate.fromJson(Map<String, dynamic> json) {
|
||||
summary =
|
||||
json['summary'] != null ? RoomSummary.fromJson(json['summary']) : null;
|
||||
state = (json['state'] != null && json['state']['events'] != null)
|
||||
? (json['state']['events'] as List)
|
||||
.map((i) => MatrixEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
timeline = json['timeline'] != null
|
||||
? TimelineUpdate.fromJson(json['timeline'])
|
||||
: null;
|
||||
|
||||
ephemeral =
|
||||
(json['ephemeral'] != null && json['ephemeral']['events'] != null)
|
||||
? (json['ephemeral']['events'] as List)
|
||||
.map((i) => BasicRoomEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
accountData =
|
||||
(json['account_data'] != null && json['account_data']['events'] != null)
|
||||
? (json['account_data']['events'] as List)
|
||||
.map((i) => BasicRoomEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
unreadNotifications = json['unread_notifications'] != null
|
||||
? UnreadNotificationCounts.fromJson(json['unread_notifications'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (summary != null) {
|
||||
data['summary'] = summary.toJson();
|
||||
}
|
||||
if (state != null) {
|
||||
data['state'] = {
|
||||
'events': state.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (timeline != null) {
|
||||
data['timeline'] = timeline.toJson();
|
||||
}
|
||||
if (ephemeral != null) {
|
||||
data['ephemeral'] = {
|
||||
'events': ephemeral.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (accountData != null) {
|
||||
data['account_data'] = {
|
||||
'events': accountData.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (unreadNotifications != null) {
|
||||
data['unread_notifications'] = unreadNotifications.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class InvitedRoomUpdate extends SyncRoomUpdate {
|
||||
List<StrippedStateEvent> inviteState;
|
||||
InvitedRoomUpdate.fromJson(Map<String, dynamic> json) {
|
||||
inviteState =
|
||||
(json['invite_state'] != null && json['invite_state']['events'] != null)
|
||||
? (json['invite_state']['events'] as List)
|
||||
.map((i) => StrippedStateEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (inviteState != null) {
|
||||
data['invite_state'] = {
|
||||
'events': inviteState.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class LeftRoomUpdate extends SyncRoomUpdate {
|
||||
List<MatrixEvent> state;
|
||||
TimelineUpdate timeline;
|
||||
List<BasicRoomEvent> accountData;
|
||||
|
||||
LeftRoomUpdate.fromJson(Map<String, dynamic> json) {
|
||||
state = (json['state'] != null && json['state']['events'] != null)
|
||||
? (json['state']['events'] as List)
|
||||
.map((i) => MatrixEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
timeline = json['timeline'] != null
|
||||
? TimelineUpdate.fromJson(json['timeline'])
|
||||
: null;
|
||||
accountData =
|
||||
(json['account_data'] != null && json['account_data']['events'] != null)
|
||||
? (json['account_data']['events'] as List)
|
||||
.map((i) => BasicRoomEvent.fromJson(i))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (state != null) {
|
||||
data['state'] = {
|
||||
'events': state.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
if (timeline != null) {
|
||||
data['timeline'] = timeline.toJson();
|
||||
}
|
||||
if (accountData != null) {
|
||||
data['account_data'] = {
|
||||
'events': accountData.map((i) => i.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineUpdate {
|
||||
List<MatrixEvent> events;
|
||||
bool limited;
|
||||
String prevBatch;
|
||||
TimelineUpdate.fromJson(Map<String, dynamic> json) {
|
||||
events = json['events'] != null
|
||||
? (json['events'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
|
||||
: null;
|
||||
limited = json['limited'];
|
||||
prevBatch = json['prev_batch'];
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (events != null) {
|
||||
data['events'] = events.map((i) => i.toJson()).toList();
|
||||
}
|
||||
if (limited != null) {
|
||||
data['limited'] = limited;
|
||||
}
|
||||
if (prevBatch != null) {
|
||||
data['prev_batch'] = prevBatch;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class UnreadNotificationCounts {
|
||||
int highlightCount;
|
||||
int notificationCount;
|
||||
UnreadNotificationCounts.fromJson(Map<String, dynamic> json) {
|
||||
highlightCount = json['highlight_count'];
|
||||
notificationCount = json['notification_count'];
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (highlightCount != null) {
|
||||
data['highlight_count'] = highlightCount;
|
||||
}
|
||||
if (notificationCount != null) {
|
||||
data['notification_count'] = notificationCount;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceListsUpdate {
|
||||
List<String> changed;
|
||||
List<String> left;
|
||||
DeviceListsUpdate.fromJson(Map<String, dynamic> json) {
|
||||
changed = List<String>.from(json['changed']);
|
||||
left = List<String>.from(json['left']);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (changed != null) {
|
||||
data['changed'] = changed;
|
||||
}
|
||||
if (left != null) {
|
||||
data['left'] = left;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
33
lib/matrix_api/model/tag.dart
Normal file
33
lib/matrix_api/model/tag.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Tag {
|
||||
double order;
|
||||
|
||||
Tag.fromJson(Map<String, dynamic> json) {
|
||||
order = json['order'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (order != null) {
|
||||
data['order'] = order;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
40
lib/matrix_api/model/third_party_identifier.dart
Normal file
40
lib/matrix_api/model/third_party_identifier.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class ThirdPartyIdentifier {
|
||||
String medium;
|
||||
String address;
|
||||
int validatedAt;
|
||||
int addedAt;
|
||||
|
||||
ThirdPartyIdentifier.fromJson(Map<String, dynamic> json) {
|
||||
medium = json['medium'];
|
||||
address = json['address'];
|
||||
validatedAt = json['validated_at'];
|
||||
addedAt = json['added_at'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['medium'] = medium;
|
||||
data['address'] = address;
|
||||
data['validated_at'] = validatedAt;
|
||||
data['added_at'] = addedAt;
|
||||
return data;
|
||||
}
|
||||
}
|
37
lib/matrix_api/model/third_party_location.dart
Normal file
37
lib/matrix_api/model/third_party_location.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class ThirdPartyLocation {
|
||||
String alias;
|
||||
String protocol;
|
||||
Map<String, dynamic> fields;
|
||||
|
||||
ThirdPartyLocation.fromJson(Map<String, dynamic> json) {
|
||||
alias = json['alias'];
|
||||
protocol = json['protocol'];
|
||||
fields = Map<String, dynamic>.from(json['fields']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['alias'] = alias;
|
||||
data['protocol'] = protocol;
|
||||
data['fields'] = fields;
|
||||
return data;
|
||||
}
|
||||
}
|
37
lib/matrix_api/model/third_party_user.dart
Normal file
37
lib/matrix_api/model/third_party_user.dart
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class ThirdPartyUser {
|
||||
String userId;
|
||||
String protocol;
|
||||
Map<String, dynamic> fields;
|
||||
|
||||
ThirdPartyUser.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['userid'];
|
||||
protocol = json['protocol'];
|
||||
fields = Map<String, dynamic>.from(json['fields']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['userid'] = userId;
|
||||
data['protocol'] = protocol;
|
||||
data['fields'] = fields;
|
||||
return data;
|
||||
}
|
||||
}
|
46
lib/matrix_api/model/timeline_history_response.dart
Normal file
46
lib/matrix_api/model/timeline_history_response.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'matrix_event.dart';
|
||||
|
||||
class TimelineHistoryResponse {
|
||||
String start;
|
||||
String end;
|
||||
List<MatrixEvent> chunk;
|
||||
List<MatrixEvent> state;
|
||||
|
||||
TimelineHistoryResponse.fromJson(Map<String, dynamic> json) {
|
||||
start = json['start'];
|
||||
end = json['end'];
|
||||
chunk = json['chunk'] != null
|
||||
? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
|
||||
: null;
|
||||
state = json['state'] != null
|
||||
? (json['state'] as List).map((i) => MatrixEvent.fromJson(i)).toList()
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (start != null) data['start'] = start;
|
||||
if (end != null) data['end'] = end;
|
||||
if (chunk != null) data['chunk'] = chunk.map((i) => i.toJson());
|
||||
if (state != null) data['state'] = state.map((i) => i.toJson());
|
||||
return data;
|
||||
}
|
||||
}
|
40
lib/matrix_api/model/turn_server_credentials.dart
Normal file
40
lib/matrix_api/model/turn_server_credentials.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class TurnServerCredentials {
|
||||
String username;
|
||||
String password;
|
||||
List<String> uris;
|
||||
int ttl;
|
||||
|
||||
TurnServerCredentials.fromJson(Map<String, dynamic> json) {
|
||||
username = json['username'];
|
||||
password = json['password'];
|
||||
uris = json['uris'].cast<String>();
|
||||
ttl = json['ttl'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['username'] = username;
|
||||
data['password'] = password;
|
||||
data['uris'] = uris;
|
||||
data['ttl'] = ttl;
|
||||
return data;
|
||||
}
|
||||
}
|
41
lib/matrix_api/model/user_search_result.dart
Normal file
41
lib/matrix_api/model/user_search_result.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'profile.dart';
|
||||
|
||||
class UserSearchResult {
|
||||
List<Profile> results;
|
||||
bool limited;
|
||||
|
||||
UserSearchResult.fromJson(Map<String, dynamic> json) {
|
||||
results = <Profile>[];
|
||||
json['results'].forEach((v) {
|
||||
results.add(Profile.fromJson(v));
|
||||
});
|
||||
|
||||
limited = json['limited'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['results'] = results.map((v) => v.toJson()).toList();
|
||||
|
||||
data['limited'] = limited;
|
||||
return data;
|
||||
}
|
||||
}
|
54
lib/matrix_api/model/well_known_informations.dart
Normal file
54
lib/matrix_api/model/well_known_informations.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class WellKnownInformations {
|
||||
MHomeserver mHomeserver;
|
||||
MHomeserver mIdentityServer;
|
||||
Map<String, dynamic> content;
|
||||
|
||||
WellKnownInformations.fromJson(Map<String, dynamic> json) {
|
||||
content = json;
|
||||
mHomeserver = json['m.homeserver'] != null
|
||||
? MHomeserver.fromJson(json['m.homeserver'])
|
||||
: null;
|
||||
mIdentityServer = json['m.identity_server'] != null
|
||||
? MHomeserver.fromJson(json['m.identity_server'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = content;
|
||||
data['m.homeserver'] = mHomeserver.toJson();
|
||||
data['m.identity_server'] = mIdentityServer.toJson();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class MHomeserver {
|
||||
String baseUrl;
|
||||
|
||||
MHomeserver.fromJson(Map<String, dynamic> json) {
|
||||
baseUrl = json['base_url'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['base_url'] = baseUrl;
|
||||
return data;
|
||||
}
|
||||
}
|
107
lib/matrix_api/model/who_is_info.dart
Normal file
107
lib/matrix_api/model/who_is_info.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class WhoIsInfo {
|
||||
String userId;
|
||||
Map<String, DeviceInfo> devices;
|
||||
|
||||
WhoIsInfo.fromJson(Map<String, dynamic> json) {
|
||||
userId = json['user_id'];
|
||||
devices = json['devices'] != null
|
||||
? (json['devices'] as Map)
|
||||
.map((k, v) => MapEntry(k, DeviceInfo.fromJson(v)))
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['user_id'] = userId;
|
||||
if (devices != null) {
|
||||
data['devices'] = devices.map((k, v) => MapEntry(k, v.toJson()));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceInfo {
|
||||
List<Sessions> sessions;
|
||||
|
||||
DeviceInfo.fromJson(Map<String, dynamic> json) {
|
||||
if (json['sessions'] != null) {
|
||||
sessions = <Sessions>[];
|
||||
json['sessions'].forEach((v) {
|
||||
sessions.add(Sessions.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (sessions != null) {
|
||||
data['sessions'] = sessions.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Sessions {
|
||||
List<Connections> connections;
|
||||
|
||||
Sessions.fromJson(Map<String, dynamic> json) {
|
||||
if (json['connections'] != null) {
|
||||
connections = <Connections>[];
|
||||
json['connections'].forEach((v) {
|
||||
connections.add(Connections.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (connections != null) {
|
||||
data['connections'] = connections.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Connections {
|
||||
String ip;
|
||||
int lastSeen;
|
||||
String userAgent;
|
||||
|
||||
Connections.fromJson(Map<String, dynamic> json) {
|
||||
ip = json['ip'];
|
||||
lastSeen = json['last_seen'];
|
||||
userAgent = json['user_agent'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (ip != null) {
|
||||
data['ip'] = ip;
|
||||
}
|
||||
if (lastSeen != null) {
|
||||
data['last_seen'] = lastSeen;
|
||||
}
|
||||
if (userAgent != null) {
|
||||
data['user_agent'] = userAgent;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import './database/database.dart' show DbAccountData;
|
||||
|
||||
/// The global private data created by this user.
|
||||
class AccountData {
|
||||
/// The json payload of the content. The content highly depends on the type.
|
||||
final Map<String, dynamic> content;
|
||||
|
||||
/// The type String of this event. For example 'm.room.message'.
|
||||
final String typeKey;
|
||||
|
||||
AccountData({this.content, this.typeKey});
|
||||
|
||||
/// Get a State event from a table row or from the event stream.
|
||||
factory AccountData.fromJson(Map<String, dynamic> jsonPayload) {
|
||||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||
return AccountData(content: content, typeKey: jsonPayload['type']);
|
||||
}
|
||||
|
||||
/// Get account data from DbAccountData
|
||||
factory AccountData.fromDb(DbAccountData dbEntry) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return AccountData(content: content, typeKey: dbEntry.type);
|
||||
}
|
||||
}
|
1564
lib/src/client.dart
1564
lib/src/client.dart
File diff suppressed because it is too large
Load diff
|
@ -2,8 +2,11 @@ import 'package:moor/moor.dart';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart' as sdk;
|
||||
import 'package:famedlysdk/matrix_api.dart' as api;
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../../matrix_api.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@UseMoor(
|
||||
|
@ -102,6 +105,22 @@ class Database extends _$Database {
|
|||
return res;
|
||||
}
|
||||
|
||||
Future<List<olm.Session>> getSingleOlmSessions(
|
||||
int clientId, String identityKey, String userId) async {
|
||||
final rows = await dbGetOlmSessions(clientId, identityKey).get();
|
||||
final res = <olm.Session>[];
|
||||
for (final row in rows) {
|
||||
try {
|
||||
var session = olm.Session();
|
||||
session.unpickle(userId, row.pickle);
|
||||
res.add(session);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not unpickle olm session: ' + e.toString());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<DbOutboundGroupSession> getDbOutboundGroupSession(
|
||||
int clientId, String roomId) async {
|
||||
final res = await dbGetOutboundGroupSession(clientId, roomId).get();
|
||||
|
@ -156,20 +175,31 @@ class Database extends _$Database {
|
|||
return roomList;
|
||||
}
|
||||
|
||||
Future<Map<String, sdk.AccountData>> getAccountData(int clientId) async {
|
||||
final newAccountData = <String, sdk.AccountData>{};
|
||||
Future<Map<String, api.BasicEvent>> getAccountData(int clientId) async {
|
||||
final newAccountData = <String, api.BasicEvent>{};
|
||||
final rawAccountData = await getAllAccountData(clientId).get();
|
||||
for (final d in rawAccountData) {
|
||||
newAccountData[d.type] = sdk.AccountData.fromDb(d);
|
||||
final content = sdk.Event.getMapFromPayload(d.content);
|
||||
newAccountData[d.type] = api.BasicEvent(
|
||||
content: content,
|
||||
type: d.type,
|
||||
);
|
||||
}
|
||||
return newAccountData;
|
||||
}
|
||||
|
||||
Future<Map<String, sdk.Presence>> getPresences(int clientId) async {
|
||||
final newPresences = <String, sdk.Presence>{};
|
||||
Future<Map<String, api.Presence>> getPresences(int clientId) async {
|
||||
final newPresences = <String, api.Presence>{};
|
||||
final rawPresences = await getAllPresences(clientId).get();
|
||||
for (final d in rawPresences) {
|
||||
newPresences[d.sender] = sdk.Presence.fromDb(d);
|
||||
// TODO: Why is this not working?
|
||||
try {
|
||||
final content = sdk.Event.getMapFromPayload(d.content);
|
||||
var presence = api.Presence.fromJson(content);
|
||||
presence.senderId = d.sender;
|
||||
presence.type = d.type;
|
||||
newPresences[d.sender] = api.Presence.fromJson(content);
|
||||
} catch (_) {}
|
||||
}
|
||||
return newPresences;
|
||||
}
|
||||
|
@ -180,7 +210,7 @@ class Database extends _$Database {
|
|||
Future<void> storeRoomUpdate(int clientId, sdk.RoomUpdate roomUpdate,
|
||||
[sdk.Room oldRoom]) async {
|
||||
final setKey = '${clientId};${roomUpdate.id}';
|
||||
if (roomUpdate.membership != sdk.Membership.leave) {
|
||||
if (roomUpdate.membership != api.Membership.leave) {
|
||||
if (!_ensuredRooms.contains(setKey)) {
|
||||
await ensureRoomExists(clientId, roomUpdate.id,
|
||||
roomUpdate.membership.toString().split('.').last);
|
||||
|
@ -241,16 +271,17 @@ class Database extends _$Database {
|
|||
/// Stores an UserUpdate object in the database. Must be called inside of
|
||||
/// [transaction].
|
||||
Future<void> storeUserEventUpdate(
|
||||
int clientId, sdk.UserUpdate userUpdate) async {
|
||||
if (userUpdate.type == 'account_data') {
|
||||
await storeAccountData(clientId, userUpdate.eventType,
|
||||
json.encode(userUpdate.content['content']));
|
||||
} else if (userUpdate.type == 'presence') {
|
||||
await storePresence(
|
||||
clientId,
|
||||
userUpdate.eventType,
|
||||
userUpdate.content['sender'],
|
||||
json.encode(userUpdate.content['content']));
|
||||
int clientId,
|
||||
String type,
|
||||
String eventType,
|
||||
Map<String, dynamic> content,
|
||||
) async {
|
||||
if (type == 'account_data') {
|
||||
await storeAccountData(
|
||||
clientId, eventType, json.encode(content['content']));
|
||||
} else if (type == 'presence') {
|
||||
await storePresence(clientId, eventType, content['sender'],
|
||||
json.encode(content['content']));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +300,7 @@ class Database extends _$Database {
|
|||
stateKey = eventContent['state_key'];
|
||||
}
|
||||
|
||||
if (eventUpdate.eventType == 'm.room.redaction') {
|
||||
if (eventUpdate.eventType == EventTypes.Redaction) {
|
||||
await redactMessage(clientId, eventUpdate);
|
||||
}
|
||||
|
||||
|
|
|
@ -5482,6 +5482,19 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
readsFrom: {olmSessions}).map(_rowToDbOlmSessions);
|
||||
}
|
||||
|
||||
Selectable<DbOlmSessions> dbGetOlmSessions(
|
||||
int client_id, String identity_key) {
|
||||
return customSelect(
|
||||
'SELECT * FROM olm_sessions WHERE client_id = :client_id AND identity_key = :identity_key',
|
||||
variables: [
|
||||
Variable.withInt(client_id),
|
||||
Variable.withString(identity_key)
|
||||
],
|
||||
readsFrom: {
|
||||
olmSessions
|
||||
}).map(_rowToDbOlmSessions);
|
||||
}
|
||||
|
||||
Future<int> storeOlmSession(
|
||||
int client_id, String identitiy_key, String session_id, String pickle) {
|
||||
return customInsert(
|
||||
|
|
|
@ -175,6 +175,7 @@ getAllUserDeviceKeys: SELECT * FROM user_device_keys WHERE client_id = :client_i
|
|||
getAllUserDeviceKeysKeys: SELECT * FROM user_device_keys_key WHERE client_id = :client_id;
|
||||
getAllUserCrossSigningKeys: SELECT * FROM user_cross_signing_keys WHERE client_id = :client_id;
|
||||
getAllOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id;
|
||||
dbGetOlmSessions: SELECT * FROM olm_sessions WHERE client_id = :client_id AND identity_key = :identity_key;
|
||||
storeOlmSession: INSERT OR REPLACE INTO olm_sessions (client_id, identity_key, session_id, pickle) VALUES (:client_id, :identitiy_key, :session_id, :pickle);
|
||||
getAllOutboundGroupSessions: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id;
|
||||
dbGetOutboundGroupSession: SELECT * FROM outbound_group_sessions WHERE client_id = :client_id AND room_id = :room_id;
|
||||
|
|
|
@ -1,75 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:famedlysdk/src/utils/receipt.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
|
||||
import '../matrix_api.dart';
|
||||
import './room.dart';
|
||||
import 'utils/matrix_localizations.dart';
|
||||
import './database/database.dart' show DbRoomState, DbEvent;
|
||||
|
||||
/// All data exchanged over Matrix is expressed as an "event". Typically each client action (e.g. sending a message) correlates with exactly one event.
|
||||
class Event {
|
||||
/// The Matrix ID for this event in the format '$localpart:server.abc'. Please not
|
||||
/// that account data, presence and other events may not have an eventId.
|
||||
final String eventId;
|
||||
|
||||
/// The json payload of the content. The content highly depends on the type.
|
||||
Map<String, dynamic> content;
|
||||
|
||||
/// The type String of this event. For example 'm.room.message'.
|
||||
final String typeKey;
|
||||
|
||||
/// The ID of the room this event belongs to.
|
||||
final String roomId;
|
||||
|
||||
/// The user who has sent this event if it is not a global account data event.
|
||||
final String senderId;
|
||||
|
||||
class Event extends MatrixEvent {
|
||||
User get sender => room.getUserByMXIDSync(senderId ?? '@unknown');
|
||||
|
||||
/// The time this event has received at the server. May be null for events like
|
||||
/// account data.
|
||||
final DateTime time;
|
||||
@Deprecated('Use [originServerTs] instead')
|
||||
DateTime get time => originServerTs;
|
||||
|
||||
/// Optional additional content for this event.
|
||||
Map<String, dynamic> unsigned;
|
||||
@Deprecated('Use [type] instead')
|
||||
String get typeKey => type;
|
||||
|
||||
/// The room this event belongs to. May be null.
|
||||
final Room room;
|
||||
|
||||
/// Optional. The previous content for this state.
|
||||
/// This will be present only for state events appearing in the timeline.
|
||||
/// If this is not a state event, or there is no previous content, this key will be null.
|
||||
Map<String, dynamic> prevContent;
|
||||
|
||||
/// Optional. This key will only be present for state events. A unique key which defines
|
||||
/// the overwriting semantics for this piece of room state.
|
||||
final String stateKey;
|
||||
|
||||
/// The status of this event.
|
||||
/// -1=ERROR
|
||||
/// 0=SENDING
|
||||
|
@ -101,17 +72,27 @@ class Event {
|
|||
|
||||
Event(
|
||||
{this.status = defaultStatus,
|
||||
this.content,
|
||||
this.typeKey,
|
||||
this.eventId,
|
||||
this.roomId,
|
||||
this.senderId,
|
||||
this.time,
|
||||
this.unsigned,
|
||||
this.prevContent,
|
||||
this.stateKey,
|
||||
Map<String, dynamic> content,
|
||||
String type,
|
||||
String eventId,
|
||||
String roomId,
|
||||
String senderId,
|
||||
DateTime originServerTs,
|
||||
Map<String, dynamic> unsigned,
|
||||
Map<String, dynamic> prevContent,
|
||||
String stateKey,
|
||||
this.room,
|
||||
this.sortOrder = 0.0});
|
||||
this.sortOrder = 0.0}) {
|
||||
this.content = content;
|
||||
this.type = type;
|
||||
this.eventId = eventId;
|
||||
this.roomId = roomId ?? room?.id;
|
||||
this.senderId = senderId;
|
||||
this.unsigned = unsigned;
|
||||
this.prevContent = prevContent;
|
||||
this.stateKey = stateKey;
|
||||
this.originServerTs = originServerTs;
|
||||
}
|
||||
|
||||
static Map<String, dynamic> getMapFromPayload(dynamic payload) {
|
||||
if (payload is String) {
|
||||
|
@ -125,6 +106,27 @@ class Event {
|
|||
return {};
|
||||
}
|
||||
|
||||
factory Event.fromMatrixEvent(
|
||||
MatrixEvent matrixEvent,
|
||||
Room room, {
|
||||
double sortOrder,
|
||||
int status,
|
||||
}) =>
|
||||
Event(
|
||||
status: status,
|
||||
content: matrixEvent.content,
|
||||
type: matrixEvent.type,
|
||||
eventId: matrixEvent.eventId,
|
||||
roomId: room.id,
|
||||
senderId: matrixEvent.senderId,
|
||||
originServerTs: matrixEvent.originServerTs,
|
||||
unsigned: matrixEvent.unsigned,
|
||||
prevContent: matrixEvent.prevContent,
|
||||
stateKey: matrixEvent.stateKey,
|
||||
room: room,
|
||||
sortOrder: sortOrder,
|
||||
);
|
||||
|
||||
/// Get a State event from a table row or from the event stream.
|
||||
factory Event.fromJson(Map<String, dynamic> jsonPayload, Room room,
|
||||
[double sortOrder]) {
|
||||
|
@ -136,11 +138,11 @@ class Event {
|
|||
stateKey: jsonPayload['state_key'],
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: jsonPayload['type'],
|
||||
type: jsonPayload['type'],
|
||||
eventId: jsonPayload['event_id'],
|
||||
roomId: jsonPayload['room_id'],
|
||||
senderId: jsonPayload['sender'],
|
||||
time: jsonPayload.containsKey('origin_server_ts')
|
||||
originServerTs: jsonPayload.containsKey('origin_server_ts')
|
||||
? DateTime.fromMillisecondsSinceEpoch(jsonPayload['origin_server_ts'])
|
||||
: DateTime.now(),
|
||||
unsigned: unsigned,
|
||||
|
@ -162,17 +164,18 @@ class Event {
|
|||
stateKey: dbEntry.stateKey,
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: dbEntry.type,
|
||||
type: dbEntry.type,
|
||||
eventId: dbEntry.eventId,
|
||||
roomId: dbEntry.roomId,
|
||||
senderId: dbEntry.sender,
|
||||
time: dbEntry.originServerTs ?? DateTime.now(),
|
||||
originServerTs: dbEntry.originServerTs ?? DateTime.now(),
|
||||
unsigned: unsigned,
|
||||
room: room,
|
||||
sortOrder: dbEntry.sortOrder ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (stateKey != null) data['state_key'] = stateKey;
|
||||
|
@ -180,116 +183,33 @@ class Event {
|
|||
data['prev_content'] = prevContent;
|
||||
}
|
||||
data['content'] = content;
|
||||
data['type'] = typeKey;
|
||||
data['type'] = type;
|
||||
data['event_id'] = eventId;
|
||||
data['room_id'] = roomId;
|
||||
data['sender'] = senderId;
|
||||
data['origin_server_ts'] = time.millisecondsSinceEpoch;
|
||||
data['origin_server_ts'] = originServerTs.millisecondsSinceEpoch;
|
||||
if (unsigned != null && unsigned.isNotEmpty) {
|
||||
data['unsigned'] = unsigned;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// The unique key of this event. For events with a [stateKey], it will be the
|
||||
/// stateKey. Otherwise it will be the [type] as a string.
|
||||
@deprecated
|
||||
String get key => stateKey == null || stateKey.isEmpty ? typeKey : stateKey;
|
||||
|
||||
User get asUser => User.fromState(
|
||||
stateKey: stateKey,
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: typeKey,
|
||||
typeKey: type,
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
senderId: senderId,
|
||||
time: time,
|
||||
originServerTs: originServerTs,
|
||||
unsigned: unsigned,
|
||||
room: room);
|
||||
|
||||
/// Get the real type.
|
||||
EventTypes get type {
|
||||
switch (typeKey) {
|
||||
case 'm.room.avatar':
|
||||
return EventTypes.RoomAvatar;
|
||||
case 'm.room.name':
|
||||
return EventTypes.RoomName;
|
||||
case 'm.room.topic':
|
||||
return EventTypes.RoomTopic;
|
||||
case 'm.room.aliases':
|
||||
return EventTypes.RoomAliases;
|
||||
case 'm.room.canonical_alias':
|
||||
return EventTypes.RoomCanonicalAlias;
|
||||
case 'm.room.create':
|
||||
return EventTypes.RoomCreate;
|
||||
case 'm.room.redaction':
|
||||
return EventTypes.Redaction;
|
||||
case 'm.room.join_rules':
|
||||
return EventTypes.RoomJoinRules;
|
||||
case 'm.room.member':
|
||||
return EventTypes.RoomMember;
|
||||
case 'm.room.power_levels':
|
||||
return EventTypes.RoomPowerLevels;
|
||||
case 'm.room.guest_access':
|
||||
return EventTypes.GuestAccess;
|
||||
case 'm.room.history_visibility':
|
||||
return EventTypes.HistoryVisibility;
|
||||
case 'm.sticker':
|
||||
return EventTypes.Sticker;
|
||||
case 'm.room.message':
|
||||
return EventTypes.Message;
|
||||
case 'm.room.encrypted':
|
||||
return EventTypes.Encrypted;
|
||||
case 'm.room.encryption':
|
||||
return EventTypes.Encryption;
|
||||
case 'm.room.tombsone':
|
||||
return EventTypes.RoomTombstone;
|
||||
case 'm.call.invite':
|
||||
return EventTypes.CallInvite;
|
||||
case 'm.call.answer':
|
||||
return EventTypes.CallAnswer;
|
||||
case 'm.call.candidates':
|
||||
return EventTypes.CallCandidates;
|
||||
case 'm.call.hangup':
|
||||
return EventTypes.CallHangup;
|
||||
}
|
||||
return EventTypes.Unknown;
|
||||
}
|
||||
|
||||
///
|
||||
MessageTypes get messageType {
|
||||
switch (content['msgtype'] ?? 'm.text') {
|
||||
case 'm.text':
|
||||
if (content.containsKey('m.relates_to')) {
|
||||
return MessageTypes.Reply;
|
||||
}
|
||||
return MessageTypes.Text;
|
||||
case 'm.notice':
|
||||
return MessageTypes.Notice;
|
||||
case 'm.emote':
|
||||
return MessageTypes.Emote;
|
||||
case 'm.image':
|
||||
return MessageTypes.Image;
|
||||
case 'm.video':
|
||||
return MessageTypes.Video;
|
||||
case 'm.audio':
|
||||
return MessageTypes.Audio;
|
||||
case 'm.file':
|
||||
return MessageTypes.File;
|
||||
case 'm.sticker':
|
||||
return MessageTypes.Sticker;
|
||||
case 'm.location':
|
||||
return MessageTypes.Location;
|
||||
case 'm.bad.encrypted':
|
||||
return MessageTypes.BadEncrypted;
|
||||
default:
|
||||
if (type == EventTypes.Message) {
|
||||
return MessageTypes.Text;
|
||||
}
|
||||
return MessageTypes.None;
|
||||
}
|
||||
}
|
||||
String get messageType => (content.containsKey('m.relates_to') &&
|
||||
content['m.relates_to']['m.in_reply_to'] != null)
|
||||
? MessageTypes.Reply
|
||||
: content['msgtype'] ?? MessageTypes.Text;
|
||||
|
||||
void setRedactionEvent(Event redactedBecause) {
|
||||
unsigned = {
|
||||
|
@ -341,9 +261,6 @@ class Event {
|
|||
/// Returns the formatted boy of this event if it has a formatted body.
|
||||
String get formattedText => content['formatted_body'] ?? '';
|
||||
|
||||
@Deprecated('Use [body] instead.')
|
||||
String getBody() => body;
|
||||
|
||||
/// Use this to get the body.
|
||||
String get body {
|
||||
if (redacted) return 'Redacted';
|
||||
|
@ -374,7 +291,7 @@ class Event {
|
|||
room.client.onEvent.add(EventUpdate(
|
||||
roomID: room.id,
|
||||
type: 'timeline',
|
||||
eventType: typeKey,
|
||||
eventType: type,
|
||||
content: {
|
||||
'event_id': eventId,
|
||||
'status': -2,
|
||||
|
@ -417,36 +334,6 @@ class Event {
|
|||
return await timeline.getEventById(replyEventId);
|
||||
}
|
||||
|
||||
Future<void> loadSession() {
|
||||
return room.loadInboundGroupSessionKeyForEvent(this);
|
||||
}
|
||||
|
||||
/// Trys to decrypt this event. Returns a m.bad.encrypted event
|
||||
/// if it fails and does nothing if the event was not encrypted.
|
||||
Event get decrypted => room.decryptGroupMessage(this);
|
||||
|
||||
/// Trys to decrypt this event and persists it in the database afterwards
|
||||
Future<Event> decryptAndStore([String updateType = 'timeline']) async {
|
||||
final newEvent = decrypted;
|
||||
if (newEvent.type == EventTypes.Encrypted) {
|
||||
return newEvent; // decryption failed
|
||||
}
|
||||
await room.client.database?.storeEventUpdate(
|
||||
room.client.id,
|
||||
EventUpdate(
|
||||
eventType: newEvent.typeKey,
|
||||
content: newEvent.toJson(),
|
||||
roomID: newEvent.roomId,
|
||||
type: updateType,
|
||||
sortOrder: newEvent.sortOrder,
|
||||
),
|
||||
);
|
||||
if (updateType != 'history') {
|
||||
room.setState(newEvent);
|
||||
}
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
/// If this event is encrypted and the decryption was not successful because
|
||||
/// the session is unknown, this requests the session key from other devices
|
||||
/// in the room. If the event is not encrypted or the decryption failed because
|
||||
|
@ -473,7 +360,7 @@ class Event {
|
|||
Future<MatrixFile> downloadAndDecryptAttachment(
|
||||
{bool getThumbnail = false}) async {
|
||||
if (![EventTypes.Message, EventTypes.Sticker].contains(type)) {
|
||||
throw ("This event has the type '$typeKey' and so it can't contain an attachment.");
|
||||
throw ("This event has the type '$type' and so it can't contain an attachment.");
|
||||
}
|
||||
if (!getThumbnail &&
|
||||
!content.containsKey('url') &&
|
||||
|
@ -741,7 +628,7 @@ class Event {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
localizedBody = i18n.unknownEvent(typeKey);
|
||||
localizedBody = i18n.unknownEvent(type);
|
||||
}
|
||||
|
||||
// Hide reply fallback
|
||||
|
@ -762,7 +649,7 @@ class Event {
|
|||
return localizedBody;
|
||||
}
|
||||
|
||||
static const Set<MessageTypes> textOnlyMessageTypes = {
|
||||
static const Set<String> textOnlyMessageTypes = {
|
||||
MessageTypes.Text,
|
||||
MessageTypes.Reply,
|
||||
MessageTypes.Notice,
|
||||
|
@ -770,43 +657,3 @@ class Event {
|
|||
MessageTypes.None,
|
||||
};
|
||||
}
|
||||
|
||||
enum MessageTypes {
|
||||
Text,
|
||||
Emote,
|
||||
Notice,
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
File,
|
||||
Location,
|
||||
Reply,
|
||||
Sticker,
|
||||
BadEncrypted,
|
||||
None,
|
||||
}
|
||||
|
||||
enum EventTypes {
|
||||
Message,
|
||||
Sticker,
|
||||
Redaction,
|
||||
RoomAliases,
|
||||
RoomCanonicalAlias,
|
||||
RoomCreate,
|
||||
RoomJoinRules,
|
||||
RoomMember,
|
||||
RoomPowerLevels,
|
||||
RoomName,
|
||||
RoomTopic,
|
||||
RoomAvatar,
|
||||
RoomTombstone,
|
||||
GuestAccess,
|
||||
HistoryVisibility,
|
||||
Encryption,
|
||||
Encrypted,
|
||||
CallInvite,
|
||||
CallAnswer,
|
||||
CallCandidates,
|
||||
CallHangup,
|
||||
Unknown,
|
||||
}
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import './database/database.dart' show DbPresence;
|
||||
|
||||
enum PresenceType { online, offline, unavailable }
|
||||
|
||||
/// Informs the client of a user's presence state change.
|
||||
class Presence {
|
||||
/// The user who sent this presence.
|
||||
final String sender;
|
||||
|
||||
/// The current display name for this user, if any.
|
||||
final String displayname;
|
||||
|
||||
/// The current avatar URL for this user, if any.
|
||||
final Uri avatarUrl;
|
||||
final bool currentlyActive;
|
||||
final int lastActiveAgo;
|
||||
final PresenceType presence;
|
||||
final String statusMsg;
|
||||
final DateTime time;
|
||||
|
||||
Presence(
|
||||
{this.sender,
|
||||
this.displayname,
|
||||
this.avatarUrl,
|
||||
this.currentlyActive,
|
||||
this.lastActiveAgo,
|
||||
this.presence,
|
||||
this.statusMsg,
|
||||
this.time});
|
||||
|
||||
Presence.fromJson(Map<String, dynamic> json)
|
||||
: sender = json['sender'],
|
||||
displayname = json['content']['displayname'],
|
||||
avatarUrl = json['content']['avatar_url'] != null
|
||||
? Uri.parse(json['content']['avatar_url'])
|
||||
: null,
|
||||
currentlyActive = json['content']['currently_active'],
|
||||
lastActiveAgo = json['content']['last_active_ago'],
|
||||
time = DateTime.fromMillisecondsSinceEpoch(
|
||||
DateTime.now().millisecondsSinceEpoch -
|
||||
(json['content']['last_active_ago'] ?? 0)),
|
||||
presence = PresenceType.values.firstWhere(
|
||||
(e) =>
|
||||
e.toString() == "PresenceType.${json['content']['presence']}",
|
||||
orElse: () => null),
|
||||
statusMsg = json['content']['status_msg'];
|
||||
|
||||
factory Presence.fromDb(DbPresence dbEntry) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return Presence(
|
||||
sender: dbEntry.sender,
|
||||
displayname: content['displayname'],
|
||||
avatarUrl: content['avatar_url'] != null
|
||||
? Uri.parse(content['avatar_url'])
|
||||
: null,
|
||||
currentlyActive: content['currently_active'],
|
||||
lastActiveAgo: content['last_active_ago'],
|
||||
time: DateTime.fromMillisecondsSinceEpoch(
|
||||
DateTime.now().millisecondsSinceEpoch -
|
||||
(content['last_active_ago'] ?? 0)),
|
||||
presence: PresenceType.values.firstWhere(
|
||||
(e) => e.toString() == "PresenceType.${content['presence']}",
|
||||
orElse: () => null),
|
||||
statusMsg: content['status_msg'],
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/account_data.dart';
|
||||
import 'package:famedlysdk/src/event.dart';
|
||||
import './database/database.dart' show DbRoomAccountData;
|
||||
|
||||
/// Stripped down events for account data and ephemrals of a room.
|
||||
class RoomAccountData extends AccountData {
|
||||
/// The user who has sent this event if it is not a global account data event.
|
||||
final String roomId;
|
||||
|
||||
final Room room;
|
||||
|
||||
RoomAccountData(
|
||||
{this.roomId, this.room, Map<String, dynamic> content, String typeKey})
|
||||
: super(content: content, typeKey: typeKey);
|
||||
|
||||
/// Get a State event from a table row or from the event stream.
|
||||
factory RoomAccountData.fromJson(
|
||||
Map<String, dynamic> jsonPayload, Room room) {
|
||||
final content = Event.getMapFromPayload(jsonPayload['content']);
|
||||
return RoomAccountData(
|
||||
content: content,
|
||||
typeKey: jsonPayload['type'],
|
||||
roomId: jsonPayload['room_id'],
|
||||
room: room);
|
||||
}
|
||||
|
||||
/// get room account data from DbRoomAccountData
|
||||
factory RoomAccountData.fromDb(DbRoomAccountData dbEntry, Room room) {
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
return RoomAccountData(
|
||||
content: content,
|
||||
typeKey: dbEntry.type,
|
||||
roomId: dbEntry.roomId,
|
||||
room: room);
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import '../../famedlysdk.dart';
|
||||
|
||||
/// Represents a new event (e.g. a message in a room) or an update for an
|
||||
/// already known event.
|
||||
class EventUpdate {
|
||||
/// Usually 'timeline', 'state' or whatever.
|
||||
final String type;
|
||||
|
||||
/// Most events belong to a room. If not, this equals to eventType.
|
||||
final String roomID;
|
||||
|
||||
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
|
||||
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
|
||||
/// informations.
|
||||
final String eventType;
|
||||
|
||||
// The json payload of the content of this event.
|
||||
final Map<String, dynamic> content;
|
||||
|
||||
// the order where to stort this event
|
||||
final double sortOrder;
|
||||
|
||||
EventUpdate(
|
||||
{this.eventType, this.roomID, this.type, this.content, this.sortOrder});
|
||||
|
||||
EventUpdate decrypt(Room room) {
|
||||
if (eventType != 'm.room.encrypted') {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
var decrpytedEvent =
|
||||
room.decryptGroupMessage(Event.fromJson(content, room, sortOrder));
|
||||
return EventUpdate(
|
||||
eventType: decrpytedEvent.typeKey,
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: decrpytedEvent.toJson(),
|
||||
sortOrder: sortOrder,
|
||||
);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import '../user.dart';
|
||||
|
||||
/// Represents a new room or an update for an
|
||||
/// already known room.
|
||||
class RoomUpdate {
|
||||
/// All rooms have an idea in the format: !uniqueid:server.abc
|
||||
final String id;
|
||||
|
||||
/// The current membership state of the user in this room.
|
||||
final Membership membership;
|
||||
|
||||
/// Represents the number of unead notifications. This probably doesn't fit the number
|
||||
/// of unread messages.
|
||||
final num notification_count;
|
||||
|
||||
// The number of unread highlighted notifications.
|
||||
final num highlight_count;
|
||||
|
||||
/// If there are too much new messages, the [homeserver] will only send the
|
||||
/// last X (default is 10) messages and set the [limitedTimelinbe] flag to true.
|
||||
final bool limitedTimeline;
|
||||
|
||||
/// Represents the current position of the client in the room history.
|
||||
final String prev_batch;
|
||||
|
||||
final RoomSummary summary;
|
||||
|
||||
RoomUpdate({
|
||||
this.id,
|
||||
this.membership,
|
||||
this.notification_count,
|
||||
this.highlight_count,
|
||||
this.limitedTimeline,
|
||||
this.prev_batch,
|
||||
this.summary,
|
||||
});
|
||||
}
|
||||
|
||||
class RoomSummary {
|
||||
List<String> mHeroes;
|
||||
int mJoinedMemberCount;
|
||||
int mInvitedMemberCount;
|
||||
|
||||
RoomSummary(
|
||||
{this.mHeroes, this.mJoinedMemberCount, this.mInvitedMemberCount});
|
||||
|
||||
RoomSummary.fromJson(Map<String, dynamic> json) {
|
||||
mHeroes = json['m.heroes']?.cast<String>();
|
||||
mJoinedMemberCount = json['m.joined_member_count'];
|
||||
mInvitedMemberCount = json['m.invited_member_count'];
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/// Represents a new global event like presence or account_data.
|
||||
class UserUpdate {
|
||||
/// Usually 'presence', 'account_data' or whatever.
|
||||
final String eventType;
|
||||
|
||||
/// See (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html]
|
||||
/// for more informations.
|
||||
final String type;
|
||||
|
||||
// The json payload of the content of this event.
|
||||
final dynamic content;
|
||||
|
||||
UserUpdate({this.eventType, this.type, this.content});
|
||||
}
|
|
@ -1,32 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
|
||||
import 'event.dart';
|
||||
import 'room.dart';
|
||||
import 'sync/event_update.dart';
|
||||
import 'sync/room_update.dart';
|
||||
import 'utils/event_update.dart';
|
||||
import 'utils/room_update.dart';
|
||||
|
||||
typedef onTimelineUpdateCallback = void Function();
|
||||
typedef onTimelineInsertCallback = void Function(int insertID);
|
||||
|
@ -100,12 +98,16 @@ class Timeline {
|
|||
void _sessionKeyReceived(String sessionId) async {
|
||||
var decryptAtLeastOneEvent = false;
|
||||
final decryptFn = () async {
|
||||
if (!room.client.encryptionEnabled) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
if (events[i].type == EventTypes.Encrypted &&
|
||||
events[i].messageType == MessageTypes.BadEncrypted &&
|
||||
events[i].content['body'] == DecryptError.UNKNOWN_SESSION &&
|
||||
events[i].content['session_id'] == sessionId) {
|
||||
events[i] = await events[i].decryptAndStore();
|
||||
events[i] = await room.client.encryption
|
||||
.decryptRoomEvent(room.id, events[i], store: true);
|
||||
if (events[i].type != EventTypes.Encrypted) {
|
||||
decryptAtLeastOneEvent = true;
|
||||
}
|
||||
|
@ -135,7 +137,7 @@ class Timeline {
|
|||
|
||||
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
|
||||
// Redaction events are handled as modification for existing events.
|
||||
if (eventUpdate.eventType == 'm.room.redaction') {
|
||||
if (eventUpdate.eventType == EventTypes.Redaction) {
|
||||
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
||||
if (eventId != null) {
|
||||
events[eventId].setRedactionEvent(Event.fromJson(
|
||||
|
@ -161,7 +163,8 @@ class Timeline {
|
|||
} else {
|
||||
Event newEvent;
|
||||
var senderUser = room
|
||||
.getState('m.room.member', eventUpdate.content['sender'])
|
||||
.getState(
|
||||
EventTypes.RoomMember, eventUpdate.content['sender'])
|
||||
?.asUser ??
|
||||
await room.client.database?.getUser(
|
||||
room.client.id, eventUpdate.content['sender'], room);
|
||||
|
|
|
@ -1,32 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:famedlysdk/src/room.dart';
|
||||
import 'package:famedlysdk/src/event.dart';
|
||||
|
||||
enum Membership { join, invite, leave, ban }
|
||||
|
||||
/// Represents a Matrix User which may be a participant in a Matrix Room.
|
||||
class User extends Event {
|
||||
factory User(
|
||||
|
@ -43,10 +37,10 @@ class User extends Event {
|
|||
return User.fromState(
|
||||
stateKey: id,
|
||||
content: content,
|
||||
typeKey: 'm.room.member',
|
||||
typeKey: EventTypes.RoomMember,
|
||||
roomId: room?.id,
|
||||
room: room,
|
||||
time: DateTime.now(),
|
||||
originServerTs: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -58,18 +52,18 @@ class User extends Event {
|
|||
String eventId,
|
||||
String roomId,
|
||||
String senderId,
|
||||
DateTime time,
|
||||
DateTime originServerTs,
|
||||
dynamic unsigned,
|
||||
Room room})
|
||||
: super(
|
||||
stateKey: stateKey,
|
||||
prevContent: prevContent,
|
||||
content: content,
|
||||
typeKey: typeKey,
|
||||
type: typeKey,
|
||||
eventId: eventId,
|
||||
roomId: roomId,
|
||||
senderId: senderId,
|
||||
time: time,
|
||||
originServerTs: originServerTs,
|
||||
unsigned: unsigned,
|
||||
room: room);
|
||||
|
||||
|
@ -142,16 +136,11 @@ class User extends Event {
|
|||
if (roomID != null) return roomID;
|
||||
|
||||
// Start a new direct chat
|
||||
final dynamic resp = await room.client.jsonRequest(
|
||||
type: HTTPType.POST,
|
||||
action: '/client/r0/createRoom',
|
||||
data: {
|
||||
'invite': [id],
|
||||
'is_direct': true,
|
||||
'preset': 'trusted_private_chat'
|
||||
});
|
||||
|
||||
final String newRoomID = resp['room_id'];
|
||||
final newRoomID = await room.client.api.createRoom(
|
||||
invite: [id],
|
||||
isDirect: true,
|
||||
preset: CreateRoomPreset.trusted_private_chat,
|
||||
);
|
||||
|
||||
if (newRoomID == null) return newRoomID;
|
||||
|
||||
|
|
|
@ -2,13 +2,15 @@ import 'dart:convert';
|
|||
import 'package:canonical_json/canonical_json.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
|
||||
import '../client.dart';
|
||||
import '../user.dart';
|
||||
import '../room.dart';
|
||||
import '../database/database.dart'
|
||||
show DbUserDeviceKey, DbUserDeviceKeysKey, DbUserCrossSigningKey;
|
||||
import '../event.dart';
|
||||
import 'key_verification.dart';
|
||||
|
||||
enum UserVerifiedStatus { verified, unknown, unknownDevice }
|
||||
|
||||
|
@ -351,12 +353,6 @@ class DeviceKeys extends SignedKey {
|
|||
@override
|
||||
Future<void> setBlocked(bool newBlocked) {
|
||||
blocked = newBlocked;
|
||||
for (var room in client.rooms) {
|
||||
if (!room.encrypted) continue;
|
||||
if (room.getParticipants().indexWhere((u) => u.id == userId) != -1) {
|
||||
room.clearOutboundGroupSession();
|
||||
}
|
||||
}
|
||||
return client.database
|
||||
?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId);
|
||||
}
|
||||
|
@ -369,10 +365,45 @@ class DeviceKeys extends SignedKey {
|
|||
identifier = dbEntry.deviceId;
|
||||
algorithms = content['algorithms'].cast<String>();
|
||||
keys = content['keys'] != null
|
||||
}) : super(userId, deviceId, algorithms, keys, signatures,
|
||||
unsigned: unsigned);
|
||||
|
||||
DeviceKeys({
|
||||
String userId,
|
||||
String deviceId,
|
||||
List<String> algorithms,
|
||||
Map<String, String> keys,
|
||||
Map<String, Map<String, String>> signatures,
|
||||
Map<String, dynamic> unsigned,
|
||||
this.verified,
|
||||
this.blocked,
|
||||
}) : super(userId, deviceId, algorithms, keys, signatures,
|
||||
unsigned: unsigned);
|
||||
|
||||
factory DeviceKeys.fromMatrixDeviceKeys(MatrixDeviceKeys matrixDeviceKeys) =>
|
||||
DeviceKeys(
|
||||
userId: matrixDeviceKeys.userId,
|
||||
deviceId: matrixDeviceKeys.deviceId,
|
||||
algorithms: matrixDeviceKeys.algorithms,
|
||||
keys: matrixDeviceKeys.keys,
|
||||
signatures: matrixDeviceKeys.signatures,
|
||||
unsigned: matrixDeviceKeys.unsigned,
|
||||
verified: false,
|
||||
blocked: false,
|
||||
);
|
||||
|
||||
static DeviceKeys fromDb(DbUserDeviceKeysKey dbEntry) {
|
||||
var deviceKeys = DeviceKeys();
|
||||
final content = Event.getMapFromPayload(dbEntry.content);
|
||||
deviceKeys.userId = dbEntry.userId;
|
||||
deviceKeys.deviceId = dbEntry.deviceId;
|
||||
deviceKeys.algorithms = content['algorithms'].cast<String>();
|
||||
deviceKeys.keys = content['keys'] != null
|
||||
? Map<String, String>.from(content['keys'])
|
||||
: null;
|
||||
signatures = content['signatures'] != null
|
||||
? Map<String, dynamic>.from(content['signatures'])
|
||||
deviceKeys.signatures = content['signatures'] != null
|
||||
? Map<String, Map<String, String>>.from((content['signatures'] as Map)
|
||||
.map((k, v) => MapEntry(k, Map<String, String>.from(v))))
|
||||
: null;
|
||||
unsigned = json['unsigned'] != null
|
||||
? Map<String, dynamic>.from(json['unsigned'])
|
||||
|
@ -401,8 +432,9 @@ class DeviceKeys extends SignedKey {
|
|||
KeyVerification startVerification() {
|
||||
final request =
|
||||
KeyVerification(client: client, userId: userId, deviceId: deviceId);
|
||||
|
||||
request.start();
|
||||
client.addKeyVerificationRequest(request);
|
||||
client.encryption.keyVerificationManager.addRequest(request);
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
|
65
lib/src/utils/event_update.dart
Normal file
65
lib/src/utils/event_update.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import '../../famedlysdk.dart';
|
||||
import '../../matrix_api.dart';
|
||||
|
||||
/// Represents a new event (e.g. a message in a room) or an update for an
|
||||
/// already known event.
|
||||
class EventUpdate {
|
||||
/// Usually 'timeline', 'state' or whatever.
|
||||
final String type;
|
||||
|
||||
/// Most events belong to a room. If not, this equals to eventType.
|
||||
final String roomID;
|
||||
|
||||
/// See (Matrix Room Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#room-events]
|
||||
/// and (Matrix Events)[https://matrix.org/docs/spec/client_server/r0.4.0.html#id89] for more
|
||||
/// informations.
|
||||
final String eventType;
|
||||
|
||||
// The json payload of the content of this event.
|
||||
final Map<String, dynamic> content;
|
||||
|
||||
// the order where to stort this event
|
||||
final double sortOrder;
|
||||
|
||||
EventUpdate(
|
||||
{this.eventType, this.roomID, this.type, this.content, this.sortOrder});
|
||||
|
||||
Future<EventUpdate> decrypt(Room room, {bool store = false}) async {
|
||||
if (eventType != EventTypes.Encrypted || !room.client.encryptionEnabled) {
|
||||
return this;
|
||||
}
|
||||
try {
|
||||
var decrpytedEvent = await room.client.encryption.decryptRoomEvent(
|
||||
room.id, Event.fromJson(content, room, sortOrder),
|
||||
store: store, updateType: type);
|
||||
return EventUpdate(
|
||||
eventType: decrpytedEvent.type,
|
||||
roomID: roomID,
|
||||
type: type,
|
||||
content: decrpytedEvent.toJson(),
|
||||
sortOrder: sortOrder,
|
||||
);
|
||||
} catch (e) {
|
||||
print('[LibOlm] Could not decrypt megolm event: ' + e.toString());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
class OpenIdCredentials {
|
||||
String accessToken;
|
||||
String tokenType;
|
||||
String matrixServerName;
|
||||
num expiresIn;
|
||||
|
||||
OpenIdCredentials.fromJson(Map<String, dynamic> json) {
|
||||
accessToken = json['access_token'];
|
||||
tokenType = json['token_type'];
|
||||
matrixServerName = json['matrix_server_name'];
|
||||
expiresIn = json['expires_in'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
var map = <String, dynamic>{};
|
||||
final data = map;
|
||||
data['access_token'] = accessToken;
|
||||
data['token_type'] = tokenType;
|
||||
data['matrix_server_name'] = matrixServerName;
|
||||
data['expires_in'] = expiresIn;
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/// Represents a user profile returned by a /profile request.
|
||||
class Profile {
|
||||
/// The user's avatar URL if they have set one, otherwise null.
|
||||
final Uri avatarUrl;
|
||||
|
||||
/// The user's display name if they have set one, otherwise null.
|
||||
final String displayname;
|
||||
|
||||
/// This API may return keys which are not limited to displayname or avatar_url.
|
||||
final Map<String, dynamic> content;
|
||||
|
||||
const Profile(this.displayname, this.avatarUrl, {this.content = const {}});
|
||||
|
||||
Profile.fromJson(Map<String, dynamic> json)
|
||||
: avatarUrl =
|
||||
json['avatar_url'] != null ? Uri.parse(json['avatar_url']) : null,
|
||||
displayname = json['displayname'],
|
||||
content = json;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) =>
|
||||
(other is Profile) &&
|
||||
avatarUrl == other.avatarUrl &&
|
||||
displayname == other.displayname;
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import '../client.dart';
|
||||
|
||||
class PublicRoomsResponse {
|
||||
List<PublicRoomEntry> publicRooms;
|
||||
final String nextBatch;
|
||||
final String prevBatch;
|
||||
final int totalRoomCountEstimate;
|
||||
Client client;
|
||||
|
||||
PublicRoomsResponse.fromJson(Map<String, dynamic> json, Client client)
|
||||
: nextBatch = json['next_batch'],
|
||||
prevBatch = json['prev_batch'],
|
||||
client = client,
|
||||
totalRoomCountEstimate = json['total_room_count_estimate'] {
|
||||
if (json['chunk'] != null) {
|
||||
publicRooms = <PublicRoomEntry>[];
|
||||
json['chunk'].forEach((v) {
|
||||
publicRooms.add(PublicRoomEntry.fromJson(v, client));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PublicRoomEntry {
|
||||
final List<String> aliases;
|
||||
final String avatarUrl;
|
||||
final bool guestCanJoin;
|
||||
final String name;
|
||||
final int numJoinedMembers;
|
||||
final String roomId;
|
||||
final String topic;
|
||||
final bool worldReadable;
|
||||
Client client;
|
||||
|
||||
Future<void> join() => client.joinRoomById(roomId);
|
||||
|
||||
PublicRoomEntry.fromJson(Map<String, dynamic> json, Client client)
|
||||
: aliases =
|
||||
json.containsKey('aliases') ? json['aliases'].cast<String>() : [],
|
||||
avatarUrl = json['avatar_url'],
|
||||
guestCanJoin = json['guest_can_join'],
|
||||
name = json['name'],
|
||||
numJoinedMembers = json['num_joined_members'],
|
||||
roomId = json['room_id'],
|
||||
topic = json['topic'],
|
||||
worldReadable = json['world_readable'],
|
||||
client = client;
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/// The global ruleset.
|
||||
class PushRules {
|
||||
final GlobalPushRules global;
|
||||
|
||||
PushRules.fromJson(Map<String, dynamic> json)
|
||||
: global = GlobalPushRules.fromJson(json['global']);
|
||||
}
|
||||
|
||||
/// The global ruleset.
|
||||
class GlobalPushRules {
|
||||
final List<PushRule> content;
|
||||
final List<PushRule> override;
|
||||
final List<PushRule> room;
|
||||
final List<PushRule> sender;
|
||||
final List<PushRule> underride;
|
||||
|
||||
GlobalPushRules.fromJson(Map<String, dynamic> json)
|
||||
: content = json.containsKey('content')
|
||||
? PushRule.fromJsonList(json['content'])
|
||||
: null,
|
||||
override = json.containsKey('override')
|
||||
? PushRule.fromJsonList(json['content'])
|
||||
: null,
|
||||
room = json.containsKey('room')
|
||||
? PushRule.fromJsonList(json['room'])
|
||||
: null,
|
||||
sender = json.containsKey('sender')
|
||||
? PushRule.fromJsonList(json['sender'])
|
||||
: null,
|
||||
underride = json.containsKey('underride')
|
||||
? PushRule.fromJsonList(json['underride'])
|
||||
: null;
|
||||
}
|
||||
|
||||
/// A single pushrule.
|
||||
class PushRule {
|
||||
final List actions;
|
||||
final bool isDefault;
|
||||
final bool enabled;
|
||||
final String ruleId;
|
||||
final List<PushRuleConditions> conditions;
|
||||
final String pattern;
|
||||
|
||||
static List<PushRule> fromJsonList(List<dynamic> list) {
|
||||
var objList = <PushRule>[];
|
||||
list.forEach((json) {
|
||||
objList.add(PushRule.fromJson(json));
|
||||
});
|
||||
return objList;
|
||||
}
|
||||
|
||||
PushRule.fromJson(Map<String, dynamic> json)
|
||||
: actions = json['actions'],
|
||||
isDefault = json['default'],
|
||||
enabled = json['enabled'],
|
||||
ruleId = json['rule_id'],
|
||||
conditions = json.containsKey('conditions')
|
||||
? PushRuleConditions.fromJsonList(json['conditions'])
|
||||
: null,
|
||||
pattern = json['pattern'];
|
||||
}
|
||||
|
||||
/// Conditions when this pushrule should be active.
|
||||
class PushRuleConditions {
|
||||
final String kind;
|
||||
final String key;
|
||||
final String pattern;
|
||||
final String is_;
|
||||
|
||||
static List<PushRuleConditions> fromJsonList(List<dynamic> list) {
|
||||
var objList = <PushRuleConditions>[];
|
||||
list.forEach((json) {
|
||||
objList.add(PushRuleConditions.fromJson(json));
|
||||
});
|
||||
return objList;
|
||||
}
|
||||
|
||||
PushRuleConditions.fromJson(Map<String, dynamic> json)
|
||||
: kind = json['kind'],
|
||||
key = json['key'],
|
||||
pattern = json['pattern'],
|
||||
is_ = json['is'];
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
class Pusher {
|
||||
String pushkey;
|
||||
String kind;
|
||||
String appId;
|
||||
String appDisplayName;
|
||||
String deviceDisplayName;
|
||||
String profileTag;
|
||||
String lang;
|
||||
PusherData data;
|
||||
|
||||
Pusher.fromJson(Map<String, dynamic> json) {
|
||||
pushkey = json['pushkey'];
|
||||
kind = json['kind'];
|
||||
appId = json['app_id'];
|
||||
appDisplayName = json['app_display_name'];
|
||||
deviceDisplayName = json['device_display_name'];
|
||||
profileTag = json['profile_tag'];
|
||||
lang = json['lang'];
|
||||
data = json['data'] != null ? PusherData.fromJson(json['data']) : null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['pushkey'] = pushkey;
|
||||
data['kind'] = kind;
|
||||
data['app_id'] = appId;
|
||||
data['app_display_name'] = appDisplayName;
|
||||
data['device_display_name'] = deviceDisplayName;
|
||||
data['profile_tag'] = profileTag;
|
||||
data['lang'] = lang;
|
||||
if (this.data != null) {
|
||||
data['data'] = this.data.toJson();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class PusherData {
|
||||
String url;
|
||||
String format;
|
||||
|
||||
PusherData.fromJson(Map<String, dynamic> json) {
|
||||
url = json['url'];
|
||||
format = json['format'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (url != null) data['url'] = url;
|
||||
if (format != null) data['format'] = format;
|
||||
return data;
|
||||
}
|
||||
}
|
92
lib/src/utils/room_update.dart
Normal file
92
lib/src/utils/room_update.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
|
||||
/// Represents a new room or an update for an
|
||||
/// already known room.
|
||||
class RoomUpdate {
|
||||
/// All rooms have an idea in the format: !uniqueid:server.abc
|
||||
final String id;
|
||||
|
||||
/// The current membership state of the user in this room.
|
||||
final Membership membership;
|
||||
|
||||
/// Represents the number of unead notifications. This probably doesn't fit the number
|
||||
/// of unread messages.
|
||||
final num notification_count;
|
||||
|
||||
// The number of unread highlighted notifications.
|
||||
final num highlight_count;
|
||||
|
||||
/// If there are too much new messages, the [homeserver] will only send the
|
||||
/// last X (default is 10) messages and set the [limitedTimelinbe] flag to true.
|
||||
final bool limitedTimeline;
|
||||
|
||||
/// Represents the current position of the client in the room history.
|
||||
final String prev_batch;
|
||||
|
||||
final RoomSummary summary;
|
||||
|
||||
RoomUpdate({
|
||||
this.id,
|
||||
this.membership,
|
||||
this.notification_count,
|
||||
this.highlight_count,
|
||||
this.limitedTimeline,
|
||||
this.prev_batch,
|
||||
this.summary,
|
||||
});
|
||||
|
||||
factory RoomUpdate.fromSyncRoomUpdate(
|
||||
SyncRoomUpdate update,
|
||||
String roomId,
|
||||
) =>
|
||||
update is JoinedRoomUpdate
|
||||
? RoomUpdate(
|
||||
id: roomId,
|
||||
membership: Membership.join,
|
||||
notification_count:
|
||||
update.unreadNotifications?.notificationCount ?? 0,
|
||||
highlight_count: update.unreadNotifications?.highlightCount ?? 0,
|
||||
limitedTimeline: update.timeline?.limited ?? false,
|
||||
prev_batch: update.timeline?.prevBatch ?? '',
|
||||
summary: update.summary,
|
||||
)
|
||||
: update is InvitedRoomUpdate
|
||||
? RoomUpdate(
|
||||
id: roomId,
|
||||
membership: Membership.invite,
|
||||
notification_count: 0,
|
||||
highlight_count: 0,
|
||||
limitedTimeline: false,
|
||||
prev_batch: '',
|
||||
summary: null,
|
||||
)
|
||||
: update is LeftRoomUpdate
|
||||
? RoomUpdate(
|
||||
id: roomId,
|
||||
membership: Membership.leave,
|
||||
notification_count: 0,
|
||||
highlight_count: 0,
|
||||
limitedTimeline: update.timeline?.limited ?? false,
|
||||
prev_batch: update.timeline?.prevBatch ?? '',
|
||||
summary: null,
|
||||
)
|
||||
: null;
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:olm/olm.dart';
|
||||
|
||||
import '../database/database.dart' show DbInboundGroupSession;
|
||||
import '../event.dart';
|
||||
|
||||
class SessionKey {
|
||||
Map<String, dynamic> content;
|
||||
Map<String, int> indexes;
|
||||
InboundGroupSession inboundGroupSession;
|
||||
final String key;
|
||||
List<dynamic> get forwardingCurve25519KeyChain =>
|
||||
content['forwarding_curve25519_key_chain'] ?? [];
|
||||
String get senderClaimedEd25519Key =>
|
||||
content['sender_claimed_ed25519_key'] ?? '';
|
||||
|
||||
SessionKey({this.content, this.inboundGroupSession, this.key, this.indexes});
|
||||
|
||||
SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key {
|
||||
final parsedContent = Event.getMapFromPayload(dbEntry.content);
|
||||
final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes);
|
||||
content =
|
||||
parsedContent != null ? Map<String, dynamic>.from(parsedContent) : null;
|
||||
indexes = parsedIndexes != null
|
||||
? Map<String, int>.from(parsedIndexes)
|
||||
: <String, int>{};
|
||||
var newInboundGroupSession = InboundGroupSession();
|
||||
newInboundGroupSession.unpickle(key, dbEntry.pickle);
|
||||
inboundGroupSession = newInboundGroupSession;
|
||||
}
|
||||
|
||||
SessionKey.fromJson(Map<String, dynamic> json, String key) : key = key {
|
||||
content = json['content'] != null
|
||||
? Map<String, dynamic>.from(json['content'])
|
||||
: null;
|
||||
indexes = json['indexes'] != null
|
||||
? Map<String, int>.from(json['indexes'])
|
||||
: <String, int>{};
|
||||
var newInboundGroupSession = InboundGroupSession();
|
||||
newInboundGroupSession.unpickle(key, json['inboundGroupSession']);
|
||||
inboundGroupSession = newInboundGroupSession;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
if (content != null) {
|
||||
data['content'] = content;
|
||||
}
|
||||
if (indexes != null) {
|
||||
data['indexes'] = indexes;
|
||||
}
|
||||
data['inboundGroupSession'] = inboundGroupSession.pickle(key);
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => json.encode(toJson());
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
import '../../matrix_api.dart';
|
||||
|
||||
/// Matrix room states are addressed by a tuple of the [type] and an
|
||||
/// optional [stateKey].
|
||||
class StatesMap {
|
||||
|
@ -11,8 +13,10 @@ class StatesMap {
|
|||
//print("[Warning] This method will be depracated in the future!");
|
||||
if (key == null) return null;
|
||||
if (key.startsWith('@') && key.contains(':')) {
|
||||
if (!states.containsKey('m.room.member')) states['m.room.member'] = {};
|
||||
return states['m.room.member'][key];
|
||||
if (!states.containsKey(EventTypes.RoomMember)) {
|
||||
states[EventTypes.RoomMember] = {};
|
||||
}
|
||||
return states[EventTypes.RoomMember][key];
|
||||
}
|
||||
if (!states.containsKey(key)) states[key] = {};
|
||||
if (states[key][''] is Event) {
|
||||
|
@ -27,8 +31,10 @@ class StatesMap {
|
|||
void operator []=(String key, Event val) {
|
||||
//print("[Warning] This method will be depracated in the future!");
|
||||
if (key.startsWith('@') && key.contains(':')) {
|
||||
if (!states.containsKey('m.room.member')) states['m.room.member'] = {};
|
||||
states['m.room.member'][key] = val;
|
||||
if (!states.containsKey(EventTypes.RoomMember)) {
|
||||
states[EventTypes.RoomMember] = {};
|
||||
}
|
||||
states[EventTypes.RoomMember][key] = val;
|
||||
}
|
||||
if (!states.containsKey(key)) states[key] = {};
|
||||
states[key][val.stateKey ?? ''] = val;
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
class ToDeviceEvent {
|
||||
String sender;
|
||||
String type;
|
||||
Map<String, dynamic> content;
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
|
||||
class ToDeviceEvent extends BasicEventWithSender {
|
||||
Map<String, dynamic> encryptedContent;
|
||||
|
||||
ToDeviceEvent({this.sender, this.type, this.content, this.encryptedContent});
|
||||
String get sender => senderId;
|
||||
set sender(String sender) => senderId = sender;
|
||||
|
||||
ToDeviceEvent({
|
||||
String sender,
|
||||
String type,
|
||||
Map<String, dynamic> content,
|
||||
this.encryptedContent,
|
||||
}) {
|
||||
senderId = sender;
|
||||
this.type = type;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
ToDeviceEvent.fromJson(Map<String, dynamic> json) {
|
||||
sender = json['sender'];
|
||||
type = json['type'];
|
||||
content = json['content'] != null
|
||||
? Map<String, dynamic>.from(json['content'])
|
||||
: null;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
var map = <String, dynamic>{};
|
||||
final data = map;
|
||||
data['sender'] = sender;
|
||||
data['type'] = type;
|
||||
if (content != null) {
|
||||
data['content'] = content;
|
||||
}
|
||||
return data;
|
||||
final event = BasicEventWithSender.fromJson(json);
|
||||
senderId = event.senderId;
|
||||
type = event.type;
|
||||
content = event.content;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,7 +33,7 @@ class ToDeviceEventDecryptionError extends ToDeviceEvent {
|
|||
this.exception,
|
||||
this.stackTrace,
|
||||
}) : super(
|
||||
sender: toDeviceEvent.sender,
|
||||
sender: toDeviceEvent.senderId,
|
||||
content: toDeviceEvent.content,
|
||||
type: toDeviceEvent.type,
|
||||
);
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
/// Credentials for the client to use when initiating calls.
|
||||
class TurnServerCredentials {
|
||||
/// The username to use.
|
||||
final String username;
|
||||
|
||||
/// The password to use.
|
||||
final String password;
|
||||
|
||||
/// A list of TURN URIs
|
||||
final List<String> uris;
|
||||
|
||||
/// The time-to-live in seconds
|
||||
final double ttl;
|
||||
|
||||
const TurnServerCredentials(
|
||||
this.username, this.password, this.uris, this.ttl);
|
||||
|
||||
TurnServerCredentials.fromJson(Map<String, dynamic> json)
|
||||
: username = json['username'],
|
||||
password = json['password'],
|
||||
uris = json['uris'].cast<String>(),
|
||||
ttl = json['ttl'];
|
||||
}
|
|
@ -1,24 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/client.dart';
|
||||
|
@ -27,8 +22,8 @@ import 'dart:core';
|
|||
extension MxcUriExtension on Uri {
|
||||
/// Returns a download Link to this content.
|
||||
String getDownloadLink(Client matrix) => isScheme('mxc')
|
||||
? matrix.homeserver != null
|
||||
? '${matrix.homeserver}/_matrix/media/r0/download/$host$path'
|
||||
? matrix.api.homeserver != null
|
||||
? '${matrix.api.homeserver.toString()}/_matrix/media/r0/download/$host$path'
|
||||
: ''
|
||||
: toString();
|
||||
|
||||
|
@ -41,8 +36,8 @@ extension MxcUriExtension on Uri {
|
|||
final methodStr = method.toString().split('.').last;
|
||||
width = width.round();
|
||||
height = height.round();
|
||||
return matrix.homeserver != null
|
||||
? '${matrix.homeserver}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr'
|
||||
return matrix.api.homeserver != null
|
||||
? '${matrix.api.homeserver.toString()}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr'
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
import '../client.dart';
|
||||
|
||||
/// Registered device for this user.
|
||||
class UserDevice {
|
||||
/// Identifier of this device.
|
||||
final String deviceId;
|
||||
|
||||
/// Display name set by the user for this device. Absent if no name has been set.
|
||||
final String displayName;
|
||||
|
||||
/// The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons).
|
||||
final String lastSeenIp;
|
||||
|
||||
/// The time when this devices was last seen. (May be a few minutes out of date, for efficiency reasons).
|
||||
final DateTime lastSeenTs;
|
||||
|
||||
final Client _client;
|
||||
|
||||
/// Updates the metadata on the given device.
|
||||
Future<void> updateMetaData(String newName) async {
|
||||
await _client.jsonRequest(
|
||||
type: HTTPType.PUT,
|
||||
action: '/client/r0/devices/$deviceId',
|
||||
data: {'display_name': newName},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Deletes the given device, and invalidates any access token associated with it.
|
||||
Future<void> deleteDevice(Map<String, dynamic> auth) async {
|
||||
await _client.jsonRequest(
|
||||
type: HTTPType.DELETE,
|
||||
action: '/client/r0/devices/$deviceId',
|
||||
data: auth != null ? {'auth': auth} : null,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
UserDevice(
|
||||
this._client, {
|
||||
this.deviceId,
|
||||
this.displayName,
|
||||
this.lastSeenIp,
|
||||
this.lastSeenTs,
|
||||
});
|
||||
|
||||
UserDevice.fromJson(Map<String, dynamic> json, Client client)
|
||||
: deviceId = json['device_id'],
|
||||
displayName = json['display_name'],
|
||||
lastSeenIp = json['last_seen_ip'],
|
||||
lastSeenTs =
|
||||
DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0),
|
||||
_client = client;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
class WellKnownInformations {
|
||||
MHomeserver mHomeserver;
|
||||
MHomeserver mIdentityServer;
|
||||
Map<String, dynamic> content;
|
||||
|
||||
WellKnownInformations.fromJson(Map<String, dynamic> json) {
|
||||
content = json;
|
||||
mHomeserver = json['m.homeserver'] != null
|
||||
? MHomeserver.fromJson(json['m.homeserver'])
|
||||
: null;
|
||||
mIdentityServer = json['m.identity_server'] != null
|
||||
? MHomeserver.fromJson(json['m.identity_server'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class MHomeserver {
|
||||
String baseUrl;
|
||||
|
||||
MHomeserver.fromJson(Map<String, dynamic> json) {
|
||||
baseUrl = json['base_url'];
|
||||
}
|
||||
}
|
|
@ -1,24 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:canonical_json/canonical_json.dart';
|
||||
|
|
|
@ -1,39 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/src/account_data.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:famedlysdk/src/client.dart';
|
||||
import 'package:famedlysdk/src/presence.dart';
|
||||
import 'package:famedlysdk/src/user.dart';
|
||||
import 'package:famedlysdk/src/sync/event_update.dart';
|
||||
import 'package:famedlysdk/src/sync/room_update.dart';
|
||||
import 'package:famedlysdk/src/sync/user_update.dart';
|
||||
import 'package:famedlysdk/src/utils/matrix_exception.dart';
|
||||
import 'package:famedlysdk/src/utils/event_update.dart';
|
||||
import 'package:famedlysdk/src/utils/room_update.dart';
|
||||
import 'package:famedlysdk/src/utils/matrix_file.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
import 'package:test/test.dart';
|
||||
|
@ -46,11 +36,11 @@ void main() {
|
|||
|
||||
Future<List<RoomUpdate>> roomUpdateListFuture;
|
||||
Future<List<EventUpdate>> eventUpdateListFuture;
|
||||
Future<List<UserUpdate>> userUpdateListFuture;
|
||||
Future<List<ToDeviceEvent>> toDeviceUpdateListFuture;
|
||||
|
||||
// key @test:fakeServer.notExisting
|
||||
const pickledOlmAccount =
|
||||
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuweStA+EKZvvHZO0SnwRp0Hw7sv8UMYvXw';
|
||||
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
|
||||
const identityKey = '7rvl3jORJkBiK4XX1e5TnGnqz068XfYJ0W++Ml63rgk';
|
||||
const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo';
|
||||
|
||||
|
@ -58,12 +48,10 @@ void main() {
|
|||
group('FluffyMatrix', () {
|
||||
/// Check if all Elements get created
|
||||
|
||||
matrix = Client('testclient', debug: true);
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
|
||||
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||
toDeviceUpdateListFuture = matrix.onToDeviceEvent.stream.toList();
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
|
@ -81,11 +69,11 @@ void main() {
|
|||
matrix.onPresence.stream.listen((Presence data) {
|
||||
presenceCounter++;
|
||||
});
|
||||
matrix.onAccountData.stream.listen((AccountData data) {
|
||||
matrix.onAccountData.stream.listen((BasicEvent data) {
|
||||
accountDataCounter++;
|
||||
});
|
||||
|
||||
expect(matrix.homeserver, null);
|
||||
expect(matrix.api.homeserver, null);
|
||||
|
||||
try {
|
||||
await matrix.checkServer('https://fakeserver.wrongaddress');
|
||||
|
@ -93,61 +81,51 @@ void main() {
|
|||
expect(exception != null, true);
|
||||
}
|
||||
await matrix.checkServer('https://fakeserver.notexisting');
|
||||
expect(matrix.homeserver, 'https://fakeserver.notexisting');
|
||||
expect(
|
||||
matrix.api.homeserver.toString(), 'https://fakeserver.notexisting');
|
||||
|
||||
final resp = await matrix
|
||||
.jsonRequest(type: HTTPType.POST, action: '/client/r0/login', data: {
|
||||
'type': 'm.login.password',
|
||||
'user': 'test',
|
||||
'password': '1234',
|
||||
'initial_device_display_name': 'Fluffy Matrix Client'
|
||||
});
|
||||
final resp = await matrix.api.login(
|
||||
type: 'm.login.password',
|
||||
user: 'test',
|
||||
password: '1234',
|
||||
initialDeviceDisplayName: 'Fluffy Matrix Client',
|
||||
);
|
||||
|
||||
final available = await matrix.usernameAvailable('testuser');
|
||||
final available = await matrix.api.usernameAvailable('testuser');
|
||||
expect(available, true);
|
||||
|
||||
Map registerResponse = await matrix.register(username: 'testuser');
|
||||
expect(registerResponse['user_id'], '@testuser:example.com');
|
||||
registerResponse =
|
||||
await matrix.register(username: 'testuser', kind: 'user');
|
||||
expect(registerResponse['user_id'], '@testuser:example.com');
|
||||
registerResponse =
|
||||
await matrix.register(username: 'testuser', kind: 'guest');
|
||||
expect(registerResponse['user_id'], '@testuser:example.com');
|
||||
|
||||
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
|
||||
var firstSyncFuture = matrix.onFirstSync.stream.first;
|
||||
var syncFuture = matrix.onSync.stream.first;
|
||||
|
||||
matrix.connect(
|
||||
newToken: resp['access_token'],
|
||||
newUserID: resp['user_id'],
|
||||
newHomeserver: matrix.homeserver,
|
||||
newToken: resp.accessToken,
|
||||
newUserID: resp.userId,
|
||||
newHomeserver: matrix.api.homeserver,
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: resp['device_id'],
|
||||
newDeviceID: resp.deviceId,
|
||||
newOlmAccount: pickledOlmAccount,
|
||||
);
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
expect(matrix.accessToken == resp['access_token'], true);
|
||||
expect(matrix.api.accessToken == resp.accessToken, true);
|
||||
expect(matrix.deviceName == 'Text Matrix Client', true);
|
||||
expect(matrix.deviceID == resp['device_id'], true);
|
||||
expect(matrix.userID == resp['user_id'], true);
|
||||
expect(matrix.deviceID == resp.deviceId, true);
|
||||
expect(matrix.userID == resp.userId, true);
|
||||
|
||||
var loginState = await loginStateFuture;
|
||||
var firstSync = await firstSyncFuture;
|
||||
dynamic sync = await syncFuture;
|
||||
var sync = await syncFuture;
|
||||
|
||||
expect(loginState, LoginState.logged);
|
||||
expect(firstSync, true);
|
||||
expect(matrix.encryptionEnabled, olmEnabled);
|
||||
if (olmEnabled) {
|
||||
expect(matrix.pickledOlmAccount, pickledOlmAccount);
|
||||
expect(matrix.identityKey, identityKey);
|
||||
expect(matrix.fingerprintKey, fingerprintKey);
|
||||
}
|
||||
expect(sync['next_batch'] == matrix.prevBatch, true);
|
||||
expect(sync.nextBatch == matrix.prevBatch, true);
|
||||
|
||||
expect(matrix.accountData.length, 3);
|
||||
expect(matrix.getDirectChatFromUserId('@bob:example.com'),
|
||||
|
@ -156,24 +134,6 @@ void main() {
|
|||
expect(matrix.directChats, matrix.accountData['m.direct'].content);
|
||||
expect(matrix.presences.length, 1);
|
||||
expect(matrix.rooms[1].ephemerals.length, 2);
|
||||
expect(matrix.rooms[1].inboundGroupSessions.length, 1);
|
||||
expect(
|
||||
matrix
|
||||
.rooms[1]
|
||||
.inboundGroupSessions[
|
||||
'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.content['session_key'],
|
||||
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw');
|
||||
if (olmEnabled) {
|
||||
expect(
|
||||
matrix
|
||||
.rooms[1]
|
||||
.inboundGroupSessions[
|
||||
'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU']
|
||||
.inboundGroupSession !=
|
||||
null,
|
||||
true);
|
||||
}
|
||||
expect(matrix.rooms[1].typingUsers.length, 1);
|
||||
expect(matrix.rooms[1].typingUsers[0].id, '@alice:example.com');
|
||||
expect(matrix.rooms[1].roomAccountData.length, 3);
|
||||
|
@ -192,14 +152,14 @@ void main() {
|
|||
expect(matrix.rooms[1].canonicalAlias,
|
||||
"#famedlyContactDiscovery:${matrix.userID.split(":")[1]}");
|
||||
final contacts = await matrix.loadFamedlyContacts();
|
||||
expect(contacts.length, 1);
|
||||
expect(contacts.length, 2);
|
||||
expect(contacts[0].senderId, '@alice:example.com');
|
||||
expect(
|
||||
matrix.presences['@alice:example.com'].presence, PresenceType.online);
|
||||
expect(matrix.presences['@alice:example.com'].presence.presence,
|
||||
PresenceType.online);
|
||||
expect(presenceCounter, 1);
|
||||
expect(accountDataCounter, 3);
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.userDeviceKeys.length, 2);
|
||||
expect(matrix.userDeviceKeys.length, 4);
|
||||
expect(matrix.userDeviceKeys['@alice:example.com'].outdated, false);
|
||||
expect(matrix.userDeviceKeys['@alice:example.com'].deviceKeys.length, 2);
|
||||
expect(
|
||||
|
@ -207,7 +167,7 @@ void main() {
|
|||
.verified,
|
||||
false);
|
||||
|
||||
await matrix.handleSync({
|
||||
await matrix.handleSync(SyncUpdate.fromJson({
|
||||
'device_lists': {
|
||||
'changed': [
|
||||
'@alice:example.com',
|
||||
|
@ -216,12 +176,12 @@ void main() {
|
|||
'@bob:example.com',
|
||||
],
|
||||
}
|
||||
});
|
||||
}));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.userDeviceKeys.length, 2);
|
||||
expect(matrix.userDeviceKeys.length, 3);
|
||||
expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true);
|
||||
|
||||
await matrix.handleSync({
|
||||
await matrix.handleSync(SyncUpdate.fromJson({
|
||||
'rooms': {
|
||||
'join': {
|
||||
'!726s6s6q:example.com': {
|
||||
|
@ -240,7 +200,7 @@ void main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
expect(
|
||||
|
@ -253,27 +213,15 @@ void main() {
|
|||
expect(altContacts[0].senderId, '@alice:example.com');
|
||||
});
|
||||
|
||||
test('Try to get ErrorResponse', () async {
|
||||
MatrixException expectedException;
|
||||
try {
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.PUT, action: '/non/existing/path');
|
||||
} on MatrixException catch (exception) {
|
||||
expectedException = exception;
|
||||
}
|
||||
expect(expectedException.error, MatrixError.M_UNRECOGNIZED);
|
||||
});
|
||||
|
||||
test('Logout', () async {
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.POST, action: '/client/r0/logout');
|
||||
await matrix.api.logout();
|
||||
|
||||
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
|
||||
|
||||
matrix.clear();
|
||||
|
||||
expect(matrix.accessToken == null, true);
|
||||
expect(matrix.homeserver == null, true);
|
||||
expect(matrix.api.accessToken == null, true);
|
||||
expect(matrix.api.homeserver == null, true);
|
||||
expect(matrix.userID == null, true);
|
||||
expect(matrix.deviceID == null, true);
|
||||
expect(matrix.deviceName == null, true);
|
||||
|
@ -288,7 +236,7 @@ void main() {
|
|||
|
||||
var roomUpdateList = await roomUpdateListFuture;
|
||||
|
||||
expect(roomUpdateList.length, 3);
|
||||
expect(roomUpdateList.length, 4);
|
||||
|
||||
expect(roomUpdateList[0].id == '!726s6s6q:example.com', true);
|
||||
expect(roomUpdateList[0].membership == Membership.join, true);
|
||||
|
@ -361,23 +309,6 @@ void main() {
|
|||
expect(eventUpdateList[11].type, 'invite_state');
|
||||
});
|
||||
|
||||
test('User Update Test', () async {
|
||||
await matrix.onUserEvent.close();
|
||||
|
||||
var eventUpdateList = await userUpdateListFuture;
|
||||
|
||||
expect(eventUpdateList.length, 4);
|
||||
|
||||
expect(eventUpdateList[0].eventType, 'm.presence');
|
||||
expect(eventUpdateList[0].type, 'presence');
|
||||
|
||||
expect(eventUpdateList[1].eventType, 'm.push_rules');
|
||||
expect(eventUpdateList[1].type, 'account_data');
|
||||
|
||||
expect(eventUpdateList[2].eventType, 'org.example.custom.config');
|
||||
expect(eventUpdateList[2].type, 'account_data');
|
||||
});
|
||||
|
||||
test('To Device Update Test', () async {
|
||||
await matrix.onToDeviceEvent.close();
|
||||
|
||||
|
@ -386,16 +317,18 @@ void main() {
|
|||
expect(eventUpdateList.length, 2);
|
||||
|
||||
expect(eventUpdateList[0].type, 'm.new_device');
|
||||
if (olmEnabled) {
|
||||
expect(eventUpdateList[1].type, 'm.room_key');
|
||||
} else {
|
||||
expect(eventUpdateList[1].type, 'm.room.encrypted');
|
||||
}
|
||||
});
|
||||
|
||||
test('Login', () async {
|
||||
matrix = Client('testclient', debug: true);
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
|
||||
roomUpdateListFuture = matrix.onRoomUpdate.stream.toList();
|
||||
eventUpdateListFuture = matrix.onEvent.stream.toList();
|
||||
userUpdateListFuture = matrix.onUserEvent.stream.toList();
|
||||
final checkResp =
|
||||
await matrix.checkServer('https://fakeServer.notExisting');
|
||||
|
||||
|
@ -405,55 +338,14 @@ void main() {
|
|||
expect(loginResp, true);
|
||||
});
|
||||
|
||||
test('createRoom', () async {
|
||||
final openId = await matrix.requestOpenIdCredentials();
|
||||
expect(openId.accessToken, 'SomeT0kenHere');
|
||||
expect(openId.tokenType, 'Bearer');
|
||||
expect(openId.matrixServerName, 'example.com');
|
||||
expect(openId.expiresIn, 3600);
|
||||
expect(openId.toJson(), {
|
||||
'access_token': 'SomeT0kenHere',
|
||||
'token_type': 'Bearer',
|
||||
'matrix_server_name': 'example.com',
|
||||
'expires_in': 3600
|
||||
});
|
||||
});
|
||||
|
||||
test('createRoom', () async {
|
||||
final users = [
|
||||
User('@alice:fakeServer.notExisting'),
|
||||
User('@bob:fakeServer.notExisting')
|
||||
];
|
||||
final newID = await matrix.createRoom(invite: users);
|
||||
expect(newID, '!1234:fakeServer.notExisting');
|
||||
});
|
||||
|
||||
test('setAvatar', () async {
|
||||
final testFile =
|
||||
MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg');
|
||||
await matrix.setAvatar(testFile);
|
||||
});
|
||||
|
||||
test('setPushers', () async {
|
||||
await matrix.setPushers('abcdefg', 'http', 'com.famedly.famedlysdk',
|
||||
'famedlySDK', 'GitLabCi', 'en', 'https://examplepushserver.com',
|
||||
format: 'event_id_only');
|
||||
});
|
||||
|
||||
test('joinRoomById', () async {
|
||||
final roomID = '1234';
|
||||
final Map<String, dynamic> resp = await matrix.joinRoomById(roomID);
|
||||
expect(resp['room_id'], roomID);
|
||||
});
|
||||
|
||||
test('requestUserDevices', () async {
|
||||
final userDevices = await matrix.requestUserDevices();
|
||||
expect(userDevices.length, 1);
|
||||
expect(userDevices.first.deviceId, 'QBUAZIFURK');
|
||||
expect(userDevices.first.displayName, 'android');
|
||||
expect(userDevices.first.lastSeenIp, '1.2.3.4');
|
||||
expect(
|
||||
userDevices.first.lastSeenTs.millisecondsSinceEpoch, 1474491775024);
|
||||
test('setMuteAllPushNotifications', () async {
|
||||
await matrix.setMuteAllPushNotifications(false);
|
||||
});
|
||||
|
||||
test('get archive', () async {
|
||||
|
@ -476,123 +368,12 @@ void main() {
|
|||
getFromRooms: false);
|
||||
expect(profile.avatarUrl.toString(), 'mxc://test');
|
||||
expect(profile.displayname, 'You got me');
|
||||
expect(profile.content['avatar_url'], profile.avatarUrl.toString());
|
||||
expect(profile.content['displayname'], profile.displayname);
|
||||
final aliceProfile =
|
||||
await matrix.getProfileFromUserId('@alice:example.com');
|
||||
expect(aliceProfile.avatarUrl.toString(),
|
||||
'mxc://example.org/SEsfnsuifSDFSSEF');
|
||||
expect(aliceProfile.displayname, 'Alice Margatroid');
|
||||
});
|
||||
|
||||
test('signJson', () {
|
||||
if (matrix.encryptionEnabled) {
|
||||
expect(matrix.fingerprintKey.isNotEmpty, true);
|
||||
expect(matrix.identityKey.isNotEmpty, true);
|
||||
var payload = <String, dynamic>{
|
||||
'unsigned': {
|
||||
'foo': 'bar',
|
||||
},
|
||||
'auth': {
|
||||
'success': true,
|
||||
'mxid': '@john.doe:example.com',
|
||||
'profile': {
|
||||
'display_name': 'John Doe',
|
||||
'three_pids': [
|
||||
{'medium': 'email', 'address': 'john.doe@example.org'},
|
||||
{'medium': 'msisdn', 'address': '123456789'}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
var payloadWithoutUnsigned = Map<String, dynamic>.from(payload);
|
||||
payloadWithoutUnsigned.remove('unsigned');
|
||||
|
||||
expect(
|
||||
matrix.checkJsonSignature(
|
||||
matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID),
|
||||
false);
|
||||
expect(
|
||||
matrix.checkJsonSignature(matrix.fingerprintKey,
|
||||
payloadWithoutUnsigned, matrix.userID, matrix.deviceID),
|
||||
false);
|
||||
payload = matrix.signJson(payload);
|
||||
payloadWithoutUnsigned = matrix.signJson(payloadWithoutUnsigned);
|
||||
expect(payload['signatures'], payloadWithoutUnsigned['signatures']);
|
||||
print(payload['signatures']);
|
||||
expect(
|
||||
matrix.checkJsonSignature(
|
||||
matrix.fingerprintKey, payload, matrix.userID, matrix.deviceID),
|
||||
true);
|
||||
expect(
|
||||
matrix.checkJsonSignature(matrix.fingerprintKey,
|
||||
payloadWithoutUnsigned, matrix.userID, matrix.deviceID),
|
||||
true);
|
||||
}
|
||||
});
|
||||
test('Track oneTimeKeys', () async {
|
||||
if (matrix.encryptionEnabled) {
|
||||
var last = matrix.lastTimeKeysUploaded ?? DateTime.now();
|
||||
await matrix.handleSync({
|
||||
'device_one_time_keys_count': {'signed_curve25519': 49}
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(
|
||||
matrix.lastTimeKeysUploaded.millisecondsSinceEpoch >
|
||||
last.millisecondsSinceEpoch,
|
||||
true);
|
||||
}
|
||||
});
|
||||
test('Test invalidate outboundGroupSessions', () async {
|
||||
if (matrix.encryptionEnabled) {
|
||||
expect(matrix.rooms[1].outboundGroupSession == null, true);
|
||||
await matrix.rooms[1].createOutboundGroupSession();
|
||||
expect(matrix.rooms[1].outboundGroupSession != null, true);
|
||||
await matrix.handleSync({
|
||||
'device_lists': {
|
||||
'changed': [
|
||||
'@alice:example.com',
|
||||
],
|
||||
'left': [
|
||||
'@bob:example.com',
|
||||
],
|
||||
}
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.rooms[1].outboundGroupSession != null, true);
|
||||
}
|
||||
});
|
||||
test('Test invalidate outboundGroupSessions', () async {
|
||||
if (matrix.encryptionEnabled) {
|
||||
await matrix.rooms[1].clearOutboundGroupSession(wipe: true);
|
||||
expect(matrix.rooms[1].outboundGroupSession == null, true);
|
||||
await matrix.rooms[1].createOutboundGroupSession();
|
||||
expect(matrix.rooms[1].outboundGroupSession != null, true);
|
||||
await matrix.handleSync({
|
||||
'rooms': {
|
||||
'join': {
|
||||
'!726s6s6q:example.com': {
|
||||
'state': {
|
||||
'events': [
|
||||
{
|
||||
'content': {'membership': 'leave'},
|
||||
'event_id': '143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'sender': '@alice:example.com',
|
||||
'state_key': '@alice:example.com',
|
||||
'type': 'm.room.member'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(matrix.rooms[1].outboundGroupSession != null, true);
|
||||
}
|
||||
});
|
||||
var deviceKeys = DeviceKeys.fromJson({
|
||||
'user_id': '@alice:example.com',
|
||||
'device_id': 'JLAFKJWSCS',
|
||||
|
@ -607,16 +388,6 @@ void main() {
|
|||
'dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA'
|
||||
}
|
||||
}
|
||||
}, matrix);
|
||||
test('startOutgoingOlmSessions', () async {
|
||||
expect(matrix.olmSessions.length, 0);
|
||||
if (olmEnabled) {
|
||||
await matrix
|
||||
.startOutgoingOlmSessions([deviceKeys], checkSignature: false);
|
||||
expect(matrix.olmSessions.length, 1);
|
||||
expect(matrix.olmSessions.entries.first.key,
|
||||
'3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI');
|
||||
}
|
||||
});
|
||||
test('sendToDevice', () async {
|
||||
await matrix.sendToDevice(
|
||||
|
@ -627,29 +398,15 @@ void main() {
|
|||
'body': 'Hello world',
|
||||
});
|
||||
});
|
||||
test('Logout when token is unknown', () async {
|
||||
var loginStateFuture = matrix.onLoginStateChanged.stream.first;
|
||||
|
||||
try {
|
||||
await matrix.jsonRequest(
|
||||
type: HTTPType.DELETE, action: '/unknown/token');
|
||||
} on MatrixException catch (exception) {
|
||||
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
|
||||
}
|
||||
|
||||
var state = await loginStateFuture;
|
||||
expect(state, LoginState.loggedOut);
|
||||
expect(matrix.isLogged(), false);
|
||||
});
|
||||
test('Test the fake store api', () async {
|
||||
var client1 = Client('testclient', debug: true);
|
||||
client1.httpClient = FakeMatrixApi();
|
||||
var client1 =
|
||||
Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
client1.database = getDatabase();
|
||||
|
||||
client1.connect(
|
||||
newToken: 'abc123',
|
||||
newUserID: '@test:fakeServer.notExisting',
|
||||
newHomeserver: 'https://fakeServer.notExisting',
|
||||
newHomeserver: Uri.parse('https://fakeServer.notExisting'),
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: 'GHTYAJCE',
|
||||
newOlmAccount: pickledOlmAccount,
|
||||
|
@ -657,40 +414,37 @@ void main() {
|
|||
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
String sessionKey;
|
||||
if (client1.encryptionEnabled) {
|
||||
await client1.rooms[1].createOutboundGroupSession();
|
||||
|
||||
sessionKey = client1.rooms[1].outboundGroupSession.session_key();
|
||||
}
|
||||
|
||||
expect(client1.isLogged(), true);
|
||||
expect(client1.rooms.length, 2);
|
||||
|
||||
var client2 = Client('testclient', debug: true);
|
||||
client2.httpClient = FakeMatrixApi();
|
||||
var client2 =
|
||||
Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
client2.database = client1.database;
|
||||
|
||||
client2.connect();
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
|
||||
expect(client2.isLogged(), true);
|
||||
expect(client2.accessToken, client1.accessToken);
|
||||
expect(client2.api.accessToken, client1.api.accessToken);
|
||||
expect(client2.userID, client1.userID);
|
||||
expect(client2.homeserver, client1.homeserver);
|
||||
expect(client2.api.homeserver, client1.api.homeserver);
|
||||
expect(client2.deviceID, client1.deviceID);
|
||||
expect(client2.deviceName, client1.deviceName);
|
||||
if (client2.encryptionEnabled) {
|
||||
await client2.rooms[1].restoreOutboundGroupSession();
|
||||
expect(client2.pickledOlmAccount, client1.pickledOlmAccount);
|
||||
expect(json.encode(client2.rooms[1].inboundGroupSessions[sessionKey]),
|
||||
json.encode(client1.rooms[1].inboundGroupSessions[sessionKey]));
|
||||
expect(client2.encryption.pickledOlmAccount,
|
||||
client1.encryption.pickledOlmAccount);
|
||||
expect(client2.rooms[1].id, client1.rooms[1].id);
|
||||
expect(client2.rooms[1].outboundGroupSession.session_key(), sessionKey);
|
||||
}
|
||||
|
||||
await client1.logout();
|
||||
await client2.logout();
|
||||
});
|
||||
test('changePassword', () async {
|
||||
await matrix.changePassword('1234', oldPassword: '123456');
|
||||
});
|
||||
|
||||
test('dispose', () async {
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
|
99
test/encryption/encrypt_decrypt_room_message_test.dart
Normal file
99
test/encryption/encrypt_decrypt_room_message_test.dart
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
|
||||
void main() {
|
||||
group('Encrypt/Decrypt room message', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
Client client;
|
||||
final roomId = '!726s6s6q:example.com';
|
||||
Room room;
|
||||
Map<String, dynamic> payload;
|
||||
final now = DateTime.now();
|
||||
|
||||
test('setupClient', () async {
|
||||
client = await getClient();
|
||||
room = client.getRoomById(roomId);
|
||||
});
|
||||
|
||||
test('encrypt payload', () async {
|
||||
payload = await client.encryption.encryptGroupMessagePayload(roomId, {
|
||||
'msgtype': 'm.text',
|
||||
'text': 'Hello foxies!',
|
||||
});
|
||||
expect(payload['algorithm'], 'm.megolm.v1.aes-sha2');
|
||||
expect(payload['ciphertext'] is String, true);
|
||||
expect(payload['device_id'], client.deviceID);
|
||||
expect(payload['sender_key'], client.identityKey);
|
||||
expect(payload['session_id'] is String, true);
|
||||
});
|
||||
|
||||
test('decrypt payload', () async {
|
||||
final encryptedEvent = Event(
|
||||
type: EventTypes.Encrypted,
|
||||
content: payload,
|
||||
roomId: roomId,
|
||||
room: room,
|
||||
originServerTs: now,
|
||||
eventId: '\$event',
|
||||
);
|
||||
final decryptedEvent =
|
||||
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
|
||||
expect(decryptedEvent.type, 'm.room.message');
|
||||
expect(decryptedEvent.content['msgtype'], 'm.text');
|
||||
expect(decryptedEvent.content['text'], 'Hello foxies!');
|
||||
});
|
||||
|
||||
test('decrypt payload nocache', () async {
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
final encryptedEvent = Event(
|
||||
type: EventTypes.Encrypted,
|
||||
content: payload,
|
||||
roomId: roomId,
|
||||
room: room,
|
||||
originServerTs: now,
|
||||
eventId: '\$event',
|
||||
);
|
||||
final decryptedEvent =
|
||||
await client.encryption.decryptRoomEvent(roomId, encryptedEvent);
|
||||
expect(decryptedEvent.type, 'm.room.message');
|
||||
expect(decryptedEvent.content['msgtype'], 'm.text');
|
||||
expect(decryptedEvent.content['text'], 'Hello foxies!');
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
120
test/encryption/encrypt_decrypt_to_device_test.dart
Normal file
120
test/encryption/encrypt_decrypt_to_device_test.dart
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
import '../fake_matrix_api.dart';
|
||||
|
||||
void main() {
|
||||
// key @othertest:fakeServer.notExisting
|
||||
const otherPickledOlmAccount =
|
||||
'VWhVApbkcilKAEGppsPDf9nNVjaK8/IxT3asSR0sYg0S5KgbfE8vXEPwoiKBX2cEvwX3OessOBOkk+ZE7TTbjlrh/KEd31p8Wo+47qj0AP+Ky+pabnhi+/rTBvZy+gfzTqUfCxZrkzfXI9Op4JnP6gYmy7dVX2lMYIIs9WCO1jcmIXiXum5jnfXu1WLfc7PZtO2hH+k9CDKosOFaXRBmsu8k/BGXPSoWqUpvu6WpEG9t5STk4FeAzA';
|
||||
|
||||
group('Encrypt/Decrypt to-device messages', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
Client client;
|
||||
var otherClient =
|
||||
Client('othertestclient', debug: true, httpClient: FakeMatrixApi());
|
||||
DeviceKeys device;
|
||||
Map<String, dynamic> payload;
|
||||
|
||||
test('setupClient', () async {
|
||||
client = await getClient();
|
||||
otherClient.database = client.database;
|
||||
await otherClient.checkServer('https://fakeServer.notExisting');
|
||||
otherClient.connect(
|
||||
newToken: 'abc',
|
||||
newUserID: '@othertest:fakeServer.notExisting',
|
||||
newHomeserver: otherClient.api.homeserver,
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: 'FOXDEVICE',
|
||||
newOlmAccount: otherPickledOlmAccount,
|
||||
);
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
device = DeviceKeys(
|
||||
userId: client.userID,
|
||||
deviceId: client.deviceID,
|
||||
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
|
||||
keys: {
|
||||
'curve25519:${client.deviceID}': client.identityKey,
|
||||
'ed25519:${client.deviceID}': client.fingerprintKey,
|
||||
},
|
||||
verified: true,
|
||||
blocked: false,
|
||||
);
|
||||
});
|
||||
|
||||
test('encryptToDeviceMessage', () async {
|
||||
payload = await otherClient.encryption
|
||||
.encryptToDeviceMessage([device], 'm.to_device', {'hello': 'foxies'});
|
||||
});
|
||||
|
||||
test('encryptToDeviceMessagePayload', () async {
|
||||
// just a hard test if nothing errors
|
||||
await otherClient.encryption.encryptToDeviceMessagePayload(
|
||||
device, 'm.to_device', {'hello': 'foxies'});
|
||||
});
|
||||
|
||||
test('decryptToDeviceEvent', () async {
|
||||
final encryptedEvent = ToDeviceEvent(
|
||||
sender: '@othertest:fakeServer.notExisting',
|
||||
type: EventTypes.Encrypted,
|
||||
content: payload[client.userID][client.deviceID],
|
||||
);
|
||||
final decryptedEvent =
|
||||
await client.encryption.decryptToDeviceEvent(encryptedEvent);
|
||||
expect(decryptedEvent.type, 'm.to_device');
|
||||
expect(decryptedEvent.content['hello'], 'foxies');
|
||||
});
|
||||
|
||||
test('decryptToDeviceEvent nocache', () async {
|
||||
client.encryption.olmManager.olmSessions.clear();
|
||||
payload = await otherClient.encryption.encryptToDeviceMessage(
|
||||
[device], 'm.to_device', {'hello': 'superfoxies'});
|
||||
final encryptedEvent = ToDeviceEvent(
|
||||
sender: '@othertest:fakeServer.notExisting',
|
||||
type: EventTypes.Encrypted,
|
||||
content: payload[client.userID][client.deviceID],
|
||||
);
|
||||
final decryptedEvent =
|
||||
await client.encryption.decryptToDeviceEvent(encryptedEvent);
|
||||
expect(decryptedEvent.type, 'm.to_device');
|
||||
expect(decryptedEvent.content['hello'], 'superfoxies');
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
await otherClient.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
223
test/encryption/key_manager_test.dart
Normal file
223
test/encryption/key_manager_test.dart
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
|
||||
void main() {
|
||||
group('Key Manager', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
Client client;
|
||||
|
||||
test('setupClient', () async {
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
test('handle new m.room_key', () async {
|
||||
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
|
||||
final validSenderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
|
||||
final sessionKey =
|
||||
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw';
|
||||
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
var event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
},
|
||||
encryptedContent: {
|
||||
'sender_key': validSessionId,
|
||||
});
|
||||
await client.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
client.encryption.keyManager.getInboundGroupSession(
|
||||
'!726s6s6q:example.com', validSessionId, validSenderKey) !=
|
||||
null,
|
||||
true);
|
||||
|
||||
// now test a few invalid scenarios
|
||||
|
||||
// not encrypted
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
});
|
||||
await client.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
client.encryption.keyManager.getInboundGroupSession(
|
||||
'!726s6s6q:example.com', validSessionId, validSenderKey) !=
|
||||
null,
|
||||
false);
|
||||
});
|
||||
|
||||
test('outbound group session', () async {
|
||||
final roomId = '!726s6s6q:example.com';
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
var sess =
|
||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
true);
|
||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
true);
|
||||
expect(
|
||||
client.encryption.keyManager.getInboundGroupSession(roomId,
|
||||
sess.outboundGroupSession.session_id(), client.identityKey) !=
|
||||
null,
|
||||
true);
|
||||
|
||||
// rotate after too many messages
|
||||
sess.sentMessages = 300;
|
||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
|
||||
// rotate if devices in room change
|
||||
sess =
|
||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
|
||||
.blocked = true;
|
||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
client.userDeviceKeys['@alice:example.com'].deviceKeys['JLAFKJWSCS']
|
||||
.blocked = false;
|
||||
|
||||
// rotate if too far in the past
|
||||
sess =
|
||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||
sess.creationTime = DateTime.now().subtract(Duration(days: 30));
|
||||
await client.encryption.keyManager.clearOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
|
||||
// force wipe
|
||||
sess =
|
||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||
await client.encryption.keyManager
|
||||
.clearOutboundGroupSession(roomId, wipe: true);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
|
||||
// load from database
|
||||
sess =
|
||||
await client.encryption.keyManager.createOutboundGroupSession(roomId);
|
||||
client.encryption.keyManager.clearOutboundGroupSessions();
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
false);
|
||||
await client.encryption.keyManager.loadOutboundGroupSession(roomId);
|
||||
expect(
|
||||
client.encryption.keyManager.getOutboundGroupSession(roomId) != null,
|
||||
true);
|
||||
});
|
||||
|
||||
test('inbound group session', () async {
|
||||
final roomId = '!726s6s6q:example.com';
|
||||
final sessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
|
||||
final senderKey = 'JBG7ZaPn54OBC7TuIEiylW3BZ+7WcGQhFBPB9pogbAg';
|
||||
final sessionContent = <String, dynamic>{
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU',
|
||||
'session_key':
|
||||
'AgAAAAAQcQ6XrFJk6Prm8FikZDqfry/NbDz8Xw7T6e+/9Yf/q3YHIPEQlzv7IZMNcYb51ifkRzFejVvtphS7wwG2FaXIp4XS2obla14iKISR0X74ugB2vyb1AydIHE/zbBQ1ic5s3kgjMFlWpu/S3FQCnCrv+DPFGEt3ERGWxIl3Bl5X53IjPyVkz65oljz2TZESwz0GH/QFvyOOm8ci0q/gceaF3S7Dmafg3dwTKYwcA5xkcc+BLyrLRzB6Hn+oMAqSNSscnm4mTeT5zYibIhrzqyUTMWr32spFtI9dNR/RFSzfCw'
|
||||
};
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||
null,
|
||||
false);
|
||||
client.encryption.keyManager
|
||||
.setInboundGroupSession(roomId, sessionId, senderKey, sessionContent);
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||
null,
|
||||
true);
|
||||
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||
null,
|
||||
true);
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession('otherroom', sessionId, senderKey) !=
|
||||
null,
|
||||
true);
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession('otherroom', 'invalid', senderKey) !=
|
||||
null,
|
||||
false);
|
||||
|
||||
client.encryption.keyManager.clearInboundGroupSessions();
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||
null,
|
||||
false);
|
||||
await client.encryption.keyManager
|
||||
.loadInboundGroupSession(roomId, sessionId, senderKey);
|
||||
expect(
|
||||
client.encryption.keyManager
|
||||
.getInboundGroupSession(roomId, sessionId, senderKey) !=
|
||||
null,
|
||||
true);
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
331
test/encryption/key_request_test.dart
Normal file
331
test/encryption/key_request_test.dart
Normal file
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
import '../fake_matrix_api.dart';
|
||||
|
||||
Map<String, dynamic> jsonDecode(dynamic payload) {
|
||||
if (payload is String) {
|
||||
try {
|
||||
return json.decode(payload);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (payload is Map<String, dynamic>) return payload;
|
||||
return {};
|
||||
}
|
||||
|
||||
void main() {
|
||||
/// All Tests related to device keys
|
||||
group('Key Request', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU';
|
||||
final validSenderKey = '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI';
|
||||
test('Create Request', () async {
|
||||
var matrix = await getClient();
|
||||
final requestRoom = matrix.getRoomById('!726s6s6q:example.com');
|
||||
await matrix.encryption.keyManager
|
||||
.request(requestRoom, 'sessionId', validSenderKey);
|
||||
var foundEvent = false;
|
||||
for (var entry in FakeMatrixApi.calledEndpoints.entries) {
|
||||
final payload = jsonDecode(entry.value.first);
|
||||
if (entry.key
|
||||
.startsWith('/client/r0/sendToDevice/m.room_key_request') &&
|
||||
(payload['messages'] is Map) &&
|
||||
(payload['messages']['@alice:example.com'] is Map) &&
|
||||
(payload['messages']['@alice:example.com']['*'] is Map)) {
|
||||
final content = payload['messages']['@alice:example.com']['*'];
|
||||
if (content['action'] == 'request' &&
|
||||
content['body']['room_id'] == '!726s6s6q:example.com' &&
|
||||
content['body']['sender_key'] == validSenderKey &&
|
||||
content['body']['session_id'] == 'sessionId') {
|
||||
foundEvent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(foundEvent, true);
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
test('Reply To Request', () async {
|
||||
var matrix = await getClient();
|
||||
matrix.setUserId('@alice:example.com'); // we need to pretend to be alice
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await matrix
|
||||
.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE']
|
||||
.setBlocked(false, matrix);
|
||||
await matrix
|
||||
.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE']
|
||||
.setVerified(true, matrix);
|
||||
// test a successful share
|
||||
var event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'sender_key': validSenderKey,
|
||||
'session_id': validSessionId,
|
||||
},
|
||||
'request_id': 'request_1',
|
||||
'requesting_device_id': 'OTHERDEVICE',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
print(FakeMatrixApi.calledEndpoints.keys.toString());
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
true);
|
||||
|
||||
// test various fail scenarios
|
||||
|
||||
// no body
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'request_id': 'request_2',
|
||||
'requesting_device_id': 'OTHERDEVICE',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
false);
|
||||
|
||||
// request by ourself
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'sender_key': validSenderKey,
|
||||
'session_id': validSessionId,
|
||||
},
|
||||
'request_id': 'request_3',
|
||||
'requesting_device_id': 'JLAFKJWSCS',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
false);
|
||||
|
||||
// device not found
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'sender_key': validSenderKey,
|
||||
'session_id': validSessionId,
|
||||
},
|
||||
'request_id': 'request_4',
|
||||
'requesting_device_id': 'blubb',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
false);
|
||||
|
||||
// unknown room
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!invalid:example.com',
|
||||
'sender_key': validSenderKey,
|
||||
'session_id': validSessionId,
|
||||
},
|
||||
'request_id': 'request_5',
|
||||
'requesting_device_id': 'OTHERDEVICE',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
false);
|
||||
|
||||
// unknwon session
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.room_key_request',
|
||||
content: {
|
||||
'action': 'request',
|
||||
'body': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'sender_key': validSenderKey,
|
||||
'session_id': 'invalid',
|
||||
},
|
||||
'request_id': 'request_6',
|
||||
'requesting_device_id': 'OTHERDEVICE',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.keys.any(
|
||||
(k) => k.startsWith('/client/r0/sendToDevice/m.room.encrypted')),
|
||||
false);
|
||||
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
test('Receive shared keys', () async {
|
||||
var matrix = await getClient();
|
||||
final requestRoom = matrix.getRoomById('!726s6s6q:example.com');
|
||||
await matrix.encryption.keyManager
|
||||
.request(requestRoom, validSessionId, validSenderKey);
|
||||
|
||||
final session = await matrix.encryption.keyManager
|
||||
.loadInboundGroupSession(
|
||||
requestRoom.id, validSessionId, validSenderKey);
|
||||
final sessionKey = session.inboundGroupSession
|
||||
.export_session(session.inboundGroupSession.first_known_index());
|
||||
matrix.encryption.keyManager.clearInboundGroupSessions();
|
||||
var event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.forwarded_room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
'sender_key': validSenderKey,
|
||||
'forwarding_curve25519_key_chain': [],
|
||||
},
|
||||
encryptedContent: {
|
||||
'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
matrix.encryption.keyManager.getInboundGroupSession(
|
||||
requestRoom.id, validSessionId, validSenderKey) !=
|
||||
null,
|
||||
true);
|
||||
|
||||
// now test a few invalid scenarios
|
||||
|
||||
// request not found
|
||||
matrix.encryption.keyManager.clearInboundGroupSessions();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.forwarded_room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
'sender_key': validSenderKey,
|
||||
'forwarding_curve25519_key_chain': [],
|
||||
},
|
||||
encryptedContent: {
|
||||
'sender_key': '3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
matrix.encryption.keyManager.getInboundGroupSession(
|
||||
requestRoom.id, validSessionId, validSenderKey) !=
|
||||
null,
|
||||
false);
|
||||
|
||||
// unknown device
|
||||
await matrix.encryption.keyManager
|
||||
.request(requestRoom, validSessionId, validSenderKey);
|
||||
matrix.encryption.keyManager.clearInboundGroupSessions();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.forwarded_room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
'sender_key': validSenderKey,
|
||||
'forwarding_curve25519_key_chain': [],
|
||||
},
|
||||
encryptedContent: {
|
||||
'sender_key': 'invalid',
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
matrix.encryption.keyManager.getInboundGroupSession(
|
||||
requestRoom.id, validSessionId, validSenderKey) !=
|
||||
null,
|
||||
false);
|
||||
|
||||
// no encrypted content
|
||||
await matrix.encryption.keyManager
|
||||
.request(requestRoom, validSessionId, validSenderKey);
|
||||
matrix.encryption.keyManager.clearInboundGroupSessions();
|
||||
event = ToDeviceEvent(
|
||||
sender: '@alice:example.com',
|
||||
type: 'm.forwarded_room_key',
|
||||
content: {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'room_id': '!726s6s6q:example.com',
|
||||
'session_id': validSessionId,
|
||||
'session_key': sessionKey,
|
||||
'sender_key': validSenderKey,
|
||||
'forwarding_curve25519_key_chain': [],
|
||||
});
|
||||
await matrix.encryption.keyManager.handleToDeviceEvent(event);
|
||||
expect(
|
||||
matrix.encryption.keyManager.getInboundGroupSession(
|
||||
requestRoom.id, validSessionId, validSenderKey) !=
|
||||
null,
|
||||
false);
|
||||
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
107
test/encryption/key_verification_test.dart
Normal file
107
test/encryption/key_verification_test.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the ChatTime
|
||||
group('Key Verification', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
Client client;
|
||||
Room room;
|
||||
var updateCounter = 0;
|
||||
KeyVerification keyVerification;
|
||||
|
||||
test('setupClient', () async {
|
||||
client = await getClient();
|
||||
room = Room(id: '!localpart:server.abc', client: client);
|
||||
keyVerification = KeyVerification(
|
||||
encryption: client.encryption,
|
||||
room: room,
|
||||
userId: '@alice:example.com',
|
||||
deviceId: 'ABCD',
|
||||
onUpdate: () => updateCounter++,
|
||||
);
|
||||
});
|
||||
|
||||
test('acceptSas', () async {
|
||||
await keyVerification.acceptSas();
|
||||
});
|
||||
test('acceptVerification', () async {
|
||||
await keyVerification.acceptVerification();
|
||||
});
|
||||
test('cancel', () async {
|
||||
await keyVerification.cancel('m.cancelcode');
|
||||
expect(keyVerification.canceled, true);
|
||||
expect(keyVerification.canceledCode, 'm.cancelcode');
|
||||
expect(keyVerification.canceledReason, null);
|
||||
});
|
||||
test('handlePayload', () async {
|
||||
await keyVerification.handlePayload('m.key.verification.request', {
|
||||
'from_device': 'AliceDevice2',
|
||||
'methods': ['m.sas.v1'],
|
||||
'timestamp': 1559598944869,
|
||||
'transaction_id': 'S0meUniqueAndOpaqueString'
|
||||
});
|
||||
await keyVerification.handlePayload('m.key.verification.start', {
|
||||
'from_device': 'BobDevice1',
|
||||
'method': 'm.sas.v1',
|
||||
'transaction_id': 'S0meUniqueAndOpaqueString'
|
||||
});
|
||||
await keyVerification.handlePayload('m.key.verification.cancel', {
|
||||
'code': 'm.user',
|
||||
'reason': 'User rejected the key verification request',
|
||||
'transaction_id': 'S0meUniqueAndOpaqueString'
|
||||
});
|
||||
});
|
||||
test('rejectSas', () async {
|
||||
await keyVerification.rejectSas();
|
||||
});
|
||||
test('rejectVerification', () async {
|
||||
await keyVerification.rejectVerification();
|
||||
});
|
||||
test('start', () async {
|
||||
await keyVerification.start();
|
||||
});
|
||||
test('verifyActivity', () async {
|
||||
final verified = await keyVerification.verifyActivity();
|
||||
expect(verified, true);
|
||||
keyVerification?.dispose();
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
116
test/encryption/olm_manager_test.dart
Normal file
116
test/encryption/olm_manager_test.dart
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:olm/olm.dart' as olm;
|
||||
|
||||
import '../fake_client.dart';
|
||||
import '../fake_matrix_api.dart';
|
||||
|
||||
void main() {
|
||||
group('Olm Manager', () {
|
||||
var olmEnabled = true;
|
||||
try {
|
||||
olm.init();
|
||||
olm.Account();
|
||||
} catch (_) {
|
||||
olmEnabled = false;
|
||||
print('[LibOlm] Failed to load LibOlm: ' + _.toString());
|
||||
}
|
||||
print('[LibOlm] Enabled: $olmEnabled');
|
||||
|
||||
if (!olmEnabled) return;
|
||||
|
||||
Client client;
|
||||
|
||||
test('setupClient', () async {
|
||||
client = await getClient();
|
||||
});
|
||||
|
||||
test('signatures', () async {
|
||||
final payload = <String, dynamic>{
|
||||
'fox': 'floof',
|
||||
};
|
||||
final signedPayload = client.encryption.olmManager.signJson(payload);
|
||||
expect(
|
||||
client.encryption.olmManager.checkJsonSignature(client.fingerprintKey,
|
||||
signedPayload, client.userID, client.deviceID),
|
||||
true);
|
||||
expect(
|
||||
client.encryption.olmManager.checkJsonSignature(
|
||||
client.fingerprintKey, payload, client.userID, client.deviceID),
|
||||
false);
|
||||
});
|
||||
|
||||
test('uploadKeys', () async {
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
final res =
|
||||
await client.encryption.olmManager.uploadKeys(uploadDeviceKeys: true);
|
||||
expect(res, true);
|
||||
var sent = json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
|
||||
expect(sent['device_keys'] != null, true);
|
||||
expect(sent['one_time_keys'] != null, true);
|
||||
expect(sent['one_time_keys'].keys.length, 66);
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await client.encryption.olmManager.uploadKeys();
|
||||
sent = json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
|
||||
expect(sent['device_keys'] != null, false);
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
await client.encryption.olmManager.uploadKeys(oldKeyCount: 20);
|
||||
sent = json.decode(
|
||||
FakeMatrixApi.calledEndpoints['/client/r0/keys/upload'].first);
|
||||
expect(sent['one_time_keys'].keys.length, 46);
|
||||
});
|
||||
|
||||
test('handleDeviceOneTimeKeysCount', () async {
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
client.encryption.olmManager
|
||||
.handleDeviceOneTimeKeysCount({'signed_curve25519': 20});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.containsKey('/client/r0/keys/upload'),
|
||||
true);
|
||||
|
||||
FakeMatrixApi.calledEndpoints.clear();
|
||||
client.encryption.olmManager
|
||||
.handleDeviceOneTimeKeysCount({'signed_curve25519': 70});
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(
|
||||
FakeMatrixApi.calledEndpoints.containsKey('/client/r0/keys/upload'),
|
||||
false);
|
||||
});
|
||||
|
||||
test('startOutgoingOlmSessions', () async {
|
||||
// start an olm session.....with ourself!
|
||||
await client.encryption.olmManager.startOutgoingOlmSessions(
|
||||
[client.userDeviceKeys[client.userID].deviceKeys[client.deviceID]]);
|
||||
expect(
|
||||
client.encryption.olmManager.olmSessions
|
||||
.containsKey(client.identityKey),
|
||||
true);
|
||||
});
|
||||
|
||||
test('dispose client', () async {
|
||||
await client.dispose(closeDatabase: true);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,33 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:famedlysdk/src/event.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'fake_matrix_api.dart';
|
||||
import 'fake_matrix_localizations.dart';
|
||||
|
||||
void main() {
|
||||
/// All Tests related to the Event
|
||||
|
@ -52,9 +50,11 @@ void main() {
|
|||
'status': 2,
|
||||
'content': contentJson,
|
||||
};
|
||||
var client = Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
var event = Event.fromJson(
|
||||
jsonObj, Room(id: '!localpart:server.abc', client: client));
|
||||
|
||||
test('Create from json', () async {
|
||||
var event = Event.fromJson(jsonObj, null);
|
||||
jsonObj.remove('status');
|
||||
jsonObj['content'] = json.decode(contentJson);
|
||||
expect(event.toJson(), jsonObj);
|
||||
|
@ -124,6 +124,7 @@ void main() {
|
|||
jsonObj['type'] = 'm.room.message';
|
||||
jsonObj['content'] = json.decode(jsonObj['content']);
|
||||
|
||||
jsonObj['content'].remove('m.relates_to');
|
||||
jsonObj['content']['msgtype'] = 'm.notice';
|
||||
event = Event.fromJson(jsonObj, null);
|
||||
expect(event.messageType, MessageTypes.Notice);
|
||||
|
@ -163,7 +164,19 @@ void main() {
|
|||
});
|
||||
|
||||
test('redact', () async {
|
||||
final room = Room(id: '1234', client: Client('testclient', debug: true));
|
||||
final redactJsonObj = Map<String, dynamic>.from(jsonObj);
|
||||
final testTypes = [
|
||||
EventTypes.RoomMember,
|
||||
EventTypes.RoomCreate,
|
||||
EventTypes.RoomJoinRules,
|
||||
EventTypes.RoomPowerLevels,
|
||||
EventTypes.RoomAliases,
|
||||
EventTypes.HistoryVisibility,
|
||||
];
|
||||
for (final testType in testTypes) {
|
||||
redactJsonObj['type'] = testType;
|
||||
final room =
|
||||
Room(id: '1234', client: Client('testclient', debug: true));
|
||||
final redactionEventJson = {
|
||||
'content': {'reason': 'Spamming'},
|
||||
'event_id': '143273582443PhrSn:example.org',
|
||||
|
@ -175,13 +188,14 @@ void main() {
|
|||
'unsigned': {'age': 1234}
|
||||
};
|
||||
var redactedBecause = Event.fromJson(redactionEventJson, room);
|
||||
var event = Event.fromJson(jsonObj, room);
|
||||
var event = Event.fromJson(redactJsonObj, room);
|
||||
event.setRedactionEvent(redactedBecause);
|
||||
expect(event.redacted, true);
|
||||
expect(event.redactedBecause.toJson(), redactedBecause.toJson());
|
||||
expect(event.content.isEmpty, true);
|
||||
redactionEventJson.remove('redacts');
|
||||
expect(event.unsigned['redacted_because'], redactionEventJson);
|
||||
}
|
||||
});
|
||||
|
||||
test('remove', () async {
|
||||
|
@ -195,8 +209,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('sendAgain', () async {
|
||||
var matrix = Client('testclient', debug: true);
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
var matrix =
|
||||
Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
await matrix.checkServer('https://fakeServer.notExisting');
|
||||
await matrix.login('test', '1234');
|
||||
|
||||
|
@ -206,14 +220,14 @@ void main() {
|
|||
event.status = -1;
|
||||
final resp2 = await event.sendAgain(txid: '1234');
|
||||
expect(resp1, null);
|
||||
expect(resp2, '42');
|
||||
expect(resp2.startsWith('\$event'), true);
|
||||
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
|
||||
test('requestKey', () async {
|
||||
var matrix = Client('testclient', debug: true);
|
||||
matrix.httpClient = FakeMatrixApi();
|
||||
var matrix =
|
||||
Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
await matrix.checkServer('https://fakeServer.notExisting');
|
||||
await matrix.login('test', '1234');
|
||||
|
||||
|
@ -227,7 +241,7 @@ void main() {
|
|||
}
|
||||
expect(exception, 'Session key not unknown');
|
||||
|
||||
event = Event.fromJson({
|
||||
var event2 = Event.fromJson({
|
||||
'event_id': id,
|
||||
'sender': senderID,
|
||||
'origin_server_ts': timestamp,
|
||||
|
@ -245,9 +259,535 @@ void main() {
|
|||
}),
|
||||
}, Room(id: '!1234:example.com', client: matrix));
|
||||
|
||||
await event.requestKey();
|
||||
await event2.requestKey();
|
||||
|
||||
await matrix.dispose(closeDatabase: true);
|
||||
});
|
||||
test('requestKey', () async {
|
||||
jsonObj['state_key'] = '@alice:example.com';
|
||||
var event = Event.fromJson(
|
||||
jsonObj, Room(id: '!localpart:server.abc', client: client));
|
||||
expect(event.stateKeyUser.id, '@alice:example.com');
|
||||
});
|
||||
test('canRedact', () async {
|
||||
expect(event.canRedact, true);
|
||||
});
|
||||
test('getLocalizedBody', () async {
|
||||
final matrix =
|
||||
Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
final room = Room(id: '!1234:example.com', client: matrix);
|
||||
var event = Event.fromJson({
|
||||
'content': {
|
||||
'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF',
|
||||
'displayname': 'Alice Margatroid',
|
||||
'membership': 'join'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'age': 1234,
|
||||
'redacted_because': {
|
||||
'content': {'reason': 'Spamming'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'redacts': '\$143273582443PhrSn:example.org',
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.redaction',
|
||||
'unsigned': {'age': 1234}
|
||||
}
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'Landing',
|
||||
'info': {
|
||||
'h': 200,
|
||||
'mimetype': 'image/png',
|
||||
'size': 73602,
|
||||
'thumbnail_info': {
|
||||
'h': 200,
|
||||
'mimetype': 'image/png',
|
||||
'size': 73602,
|
||||
'w': 140
|
||||
},
|
||||
'thumbnail_url': 'mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP',
|
||||
'w': 140
|
||||
},
|
||||
'url': 'mxc://matrix.org/sHhqkFCvSkFwtmvtETOtKnLP'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.sticker',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'reason': 'Spamming'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'redacts': '\$143273582443PhrSn:example.org',
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.redaction',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'aliases': ['#somewhere:example.org', '#another:example.org']
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': 'example.org',
|
||||
'type': 'm.room.aliases',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'aliases': ['#somewhere:example.org', '#another:example.org']
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': 'example.org',
|
||||
'type': 'm.room.aliases',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'alias': '#somewhere:localhost'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.canonical_alias',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'creator': '@example:example.org',
|
||||
'm.federate': true,
|
||||
'predecessor': {
|
||||
'event_id': '\$something:example.org',
|
||||
'room_id': '!oldroom:example.org'
|
||||
},
|
||||
'room_version': '1'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.create',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'This room has been replaced',
|
||||
'replacement_room': '!newroom:example.org'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.tombstone',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'join_rule': 'public'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.join_rules',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF',
|
||||
'displayname': 'Alice Margatroid',
|
||||
'membership': 'join'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'invite'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member'
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'leave'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'join'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'ban'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'join'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'join'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'invite'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'invite'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'join'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'leave'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'invite'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'membership': 'leave'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@alice:example.org',
|
||||
'state_key': '@alice:example.org',
|
||||
'type': 'm.room.member',
|
||||
'unsigned': {
|
||||
'prev_content': {'membership': 'invite'},
|
||||
}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'ban': 50,
|
||||
'events': {'m.room.name': 100, 'm.room.power_levels': 100},
|
||||
'events_default': 0,
|
||||
'invite': 50,
|
||||
'kick': 50,
|
||||
'notifications': {'room': 20},
|
||||
'redact': 50,
|
||||
'state_default': 50,
|
||||
'users': {'@example:localhost': 100},
|
||||
'users_default': 0
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.power_levels',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'name': 'The room name'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.name',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'topic': 'A room topic'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.topic',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'info': {'h': 398, 'mimetype': 'image/jpeg', 'size': 31037, 'w': 394},
|
||||
'url': 'mxc://example.org/JWEIFJgwEIhweiWJE'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.avatar',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {'history_visibility': 'shared'},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.history_visibility',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'algorithm': 'm.megolm.v1.aes-sha2',
|
||||
'rotation_period_ms': 604800000,
|
||||
'rotation_period_msgs': 100
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'state_key': '',
|
||||
'type': 'm.room.encryption',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
|
||||
'Example activatedEndToEndEncryption. needPantalaimonWarning');
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'This is an example text message',
|
||||
'format': 'org.matrix.custom.html',
|
||||
'formatted_body': '<b>This is an example text message</b>',
|
||||
'msgtype': 'm.text'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
|
||||
'This is an example text message');
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'thinks this is an example emote',
|
||||
'format': 'org.matrix.custom.html',
|
||||
'formatted_body': 'thinks <b>this</b> is an example emote',
|
||||
'msgtype': 'm.emote'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
|
||||
'* thinks this is an example emote');
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'This is an example notice',
|
||||
'format': 'org.matrix.custom.html',
|
||||
'formatted_body': 'This is an <strong>example</strong> notice',
|
||||
'msgtype': 'm.notice'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()),
|
||||
'This is an example notice');
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'filename.jpg',
|
||||
'info': {'h': 398, 'mimetype': 'image/jpeg', 'size': 31037, 'w': 394},
|
||||
'msgtype': 'm.image',
|
||||
'url': 'mxc://example.org/JWEIFJgwEIhweiWJE'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'something-important.doc',
|
||||
'filename': 'something-important.doc',
|
||||
'info': {'mimetype': 'application/msword', 'size': 46144},
|
||||
'msgtype': 'm.file',
|
||||
'url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'Bee Gees - Stayin Alive',
|
||||
'info': {
|
||||
'duration': 2140786,
|
||||
'mimetype': 'audio/mpeg',
|
||||
'size': 1563685
|
||||
},
|
||||
'msgtype': 'm.audio',
|
||||
'url': 'mxc://example.org/ffed755USFFxlgbQYZGtryd'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'Big Ben, London, UK',
|
||||
'geo_uri': 'geo:51.5008,0.1247',
|
||||
'info': {
|
||||
'thumbnail_info': {
|
||||
'h': 300,
|
||||
'mimetype': 'image/jpeg',
|
||||
'size': 46144,
|
||||
'w': 300
|
||||
},
|
||||
'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe'
|
||||
},
|
||||
'msgtype': 'm.location'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
|
||||
event = Event.fromJson({
|
||||
'content': {
|
||||
'body': 'Gangnam Style',
|
||||
'info': {
|
||||
'duration': 2140786,
|
||||
'h': 320,
|
||||
'mimetype': 'video/mp4',
|
||||
'size': 1563685,
|
||||
'thumbnail_info': {
|
||||
'h': 300,
|
||||
'mimetype': 'image/jpeg',
|
||||
'size': 46144,
|
||||
'w': 300
|
||||
},
|
||||
'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe',
|
||||
'w': 480
|
||||
},
|
||||
'msgtype': 'm.video',
|
||||
'url': 'mxc://example.org/a526eYUSFFxlgbQYZmo442'
|
||||
},
|
||||
'event_id': '\$143273582443PhrSn:example.org',
|
||||
'origin_server_ts': 1432735824653,
|
||||
'room_id': '!jEsUZKDJdhlrceRyVU:example.org',
|
||||
'sender': '@example:example.org',
|
||||
'type': 'm.room.message',
|
||||
'unsigned': {'age': 1234}
|
||||
}, room);
|
||||
expect(event.getLocalizedBody(FakeMatrixLocalizations()), null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
48
test/fake_client.dart
Normal file
48
test/fake_client.dart
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
import 'fake_matrix_api.dart';
|
||||
import 'fake_database.dart';
|
||||
|
||||
// key @test:fakeServer.notExisting
|
||||
const pickledOlmAccount =
|
||||
'N2v1MkIFGcl0mQpo2OCwSopxPQJ0wnl7oe7PKiT4141AijfdTIhRu+ceXzXKy3Kr00nLqXtRv7kid6hU4a+V0rfJWLL0Y51+3Rp/ORDVnQy+SSeo6Fn4FHcXrxifJEJ0djla5u98fBcJ8BSkhIDmtXRPi5/oJAvpiYn+8zMjFHobOeZUAxYR0VfQ9JzSYBsSovoQ7uFkNks1M4EDUvHtuyg3RxViwdNxs3718fyAqQ/VSwbXsY0Nl+qQbF+nlVGHenGqk5SuNl1P6e1PzZxcR0IfXA94Xij1Ob5gDv5YH4UCn9wRMG0abZsQP0YzpDM0FLaHSCyo9i5JD/vMlhH+nZWrgAzPPCTNGYewNV8/h3c+VyJh8ZTx/fVi6Yq46Fv+27Ga2ETRZ3Qn+Oyx6dLBjnBZ9iUvIhqpe2XqaGA1PopOz8iDnaZitw';
|
||||
|
||||
Future<Client> getClient() async {
|
||||
final client = Client('testclient', debug: true, httpClient: FakeMatrixApi());
|
||||
client.database = getDatabase();
|
||||
await client.checkServer('https://fakeServer.notExisting');
|
||||
final resp = await client.api.login(
|
||||
type: 'm.login.password',
|
||||
user: 'test',
|
||||
password: '1234',
|
||||
initialDeviceDisplayName: 'Fluffy Matrix Client',
|
||||
);
|
||||
client.connect(
|
||||
newToken: resp.accessToken,
|
||||
newUserID: resp.userId,
|
||||
newHomeserver: client.api.homeserver,
|
||||
newDeviceName: 'Text Matrix Client',
|
||||
newDeviceID: resp.deviceId,
|
||||
newOlmAccount: pickledOlmAccount,
|
||||
);
|
||||
await Future.delayed(Duration(milliseconds: 10));
|
||||
return client;
|
||||
}
|
|
@ -1,6 +1,26 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:moor/moor.dart';
|
||||
import 'package:moor_ffi/moor_ffi.dart' as moor;
|
||||
|
||||
Database getDatabase() {
|
||||
moorRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
return Database(moor.VmDatabase.memory());
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
309
test/fake_matrix_localizations.dart
Normal file
309
test/fake_matrix_localizations.dart
Normal file
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
class FakeMatrixLocalizations extends MatrixLocalizations {
|
||||
@override
|
||||
String acceptedTheInvitation(String targetName) {
|
||||
// TODO: implement acceptedTheInvitation
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String activatedEndToEndEncryption(String senderName) {
|
||||
// TODO: implement activatedEndToEndEncryption
|
||||
return '$senderName activatedEndToEndEncryption';
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement anyoneCanJoin
|
||||
String get anyoneCanJoin => null;
|
||||
|
||||
@override
|
||||
String bannedUser(String senderName, String targetName) {
|
||||
// TODO: implement bannedUser
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatAvatar(String senderName) {
|
||||
// TODO: implement changedTheChatAvatar
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatDescriptionTo(String senderName, String content) {
|
||||
// TODO: implement changedTheChatDescriptionTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatNameTo(String senderName, String content) {
|
||||
// TODO: implement changedTheChatNameTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheChatPermissions(String senderName) {
|
||||
// TODO: implement changedTheChatPermissions
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheDisplaynameTo(String targetName, String newDisplayname) {
|
||||
// TODO: implement changedTheDisplaynameTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheGuestAccessRules(String senderName) {
|
||||
// TODO: implement changedTheGuestAccessRules
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheGuestAccessRulesTo(
|
||||
String senderName, String localizedString) {
|
||||
// TODO: implement changedTheGuestAccessRulesTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheHistoryVisibility(String senderName) {
|
||||
// TODO: implement changedTheHistoryVisibility
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheHistoryVisibilityTo(
|
||||
String senderName, String localizedString) {
|
||||
// TODO: implement changedTheHistoryVisibilityTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheJoinRules(String senderName) {
|
||||
// TODO: implement changedTheJoinRules
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheJoinRulesTo(String senderName, String localizedString) {
|
||||
// TODO: implement changedTheJoinRulesTo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheProfileAvatar(String targetName) {
|
||||
// TODO: implement changedTheProfileAvatar
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheRoomAliases(String senderName) {
|
||||
// TODO: implement changedTheRoomAliases
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String changedTheRoomInvitationLink(String senderName) {
|
||||
// TODO: implement changedTheRoomInvitationLink
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement channelCorruptedDecryptError
|
||||
String get channelCorruptedDecryptError => null;
|
||||
|
||||
@override
|
||||
String couldNotDecryptMessage(String errorText) {
|
||||
// TODO: implement couldNotDecryptMessage
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String createdTheChat(String senderName) {
|
||||
// TODO: implement createdTheChat
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement emptyChat
|
||||
String get emptyChat => null;
|
||||
|
||||
@override
|
||||
// TODO: implement encryptionNotEnabled
|
||||
String get encryptionNotEnabled => null;
|
||||
|
||||
@override
|
||||
// TODO: implement fromJoining
|
||||
String get fromJoining => null;
|
||||
|
||||
@override
|
||||
// TODO: implement fromTheInvitation
|
||||
String get fromTheInvitation => null;
|
||||
|
||||
@override
|
||||
String groupWith(String displayname) {
|
||||
// TODO: implement groupWith
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement guestsAreForbidden
|
||||
String get guestsAreForbidden => null;
|
||||
|
||||
@override
|
||||
// TODO: implement guestsCanJoin
|
||||
String get guestsCanJoin => null;
|
||||
|
||||
@override
|
||||
String hasWithdrawnTheInvitationFor(String senderName, String targetName) {
|
||||
// TODO: implement hasWithdrawnTheInvitationFor
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String invitedUser(String senderName, String targetName) {
|
||||
// TODO: implement invitedUser
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement invitedUsersOnly
|
||||
String get invitedUsersOnly => null;
|
||||
|
||||
@override
|
||||
String joinedTheChat(String targetName) {
|
||||
// TODO: implement joinedTheChat
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String kicked(String senderName, String targetName) {
|
||||
// TODO: implement kicked
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String kickedAndBanned(String senderName, String targetName) {
|
||||
// TODO: implement kickedAndBanned
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement needPantalaimonWarning
|
||||
String get needPantalaimonWarning => 'needPantalaimonWarning';
|
||||
|
||||
@override
|
||||
// TODO: implement noPermission
|
||||
String get noPermission => 'noPermission';
|
||||
|
||||
@override
|
||||
String redactedAnEvent(String senderName) {
|
||||
// TODO: implement redactedAnEvent
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String rejectedTheInvitation(String targetName) {
|
||||
// TODO: implement rejectedTheInvitation
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String removedBy(String calcDisplayname) {
|
||||
// TODO: implement removedBy
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement roomHasBeenUpgraded
|
||||
String get roomHasBeenUpgraded => null;
|
||||
|
||||
@override
|
||||
String sentAFile(String senderName) {
|
||||
// TODO: implement sentAFile
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAPicture(String senderName) {
|
||||
// TODO: implement sentAPicture
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String sentASticker(String senderName) {
|
||||
// TODO: implement sentASticker
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAVideo(String senderName) {
|
||||
// TODO: implement sentAVideo
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAnAudio(String senderName) {
|
||||
// TODO: implement sentAnAudio
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String sharedTheLocation(String senderName) {
|
||||
// TODO: implement sharedTheLocation
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String unbannedUser(String senderName, String targetName) {
|
||||
// TODO: implement unbannedUser
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement unknownEncryptionAlgorithm
|
||||
String get unknownEncryptionAlgorithm => null;
|
||||
|
||||
@override
|
||||
String unknownEvent(String typeKey) {
|
||||
// TODO: implement unknownEvent
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
String userLeftTheChat(String targetName) {
|
||||
// TODO: implement userLeftTheChat
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement visibleForAllParticipants
|
||||
String get visibleForAllParticipants => null;
|
||||
|
||||
@override
|
||||
// TODO: implement visibleForEveryone
|
||||
String get visibleForEveryone => null;
|
||||
|
||||
@override
|
||||
// TODO: implement you
|
||||
String get you => null;
|
||||
}
|
|
@ -1,3 +1,21 @@
|
|||
/*
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/src/utils/markdown.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
|
1518
test/matrix_api_test.dart
Normal file
1518
test/matrix_api_test.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,24 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||
* Ansible inventory script used at Famedly GmbH for managing many hosts
|
||||
* Copyright (C) 2019, 2020 Famedly GmbH
|
||||
*
|
||||
* Authors:
|
||||
* Christian Pauly <krille@famedly.com>
|
||||
* Marcel Radzio <mtrnord@famedly.com>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This file is part of famedlysdk.
|
||||
*
|
||||
* famedlysdk is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* famedlysdk is distributed in the hope that it will be useful,
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue