diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index 92cbb86..c8e2249 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -167,7 +167,7 @@ class CrossSigning { } if (signedKeys.isNotEmpty) { // post our new keys! - await client.api.uploadKeySignatures(signedKeys); + await client.uploadKeySignatures(signedKeys); } } diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index a2a3e55..a0e1809 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -44,7 +44,7 @@ class KeyManager { encryption.ssss.setValidator(MEGOLM_KEY, (String secret) async { final keyObj = olm.PkDecryption(); try { - final info = await client.api.getRoomKeysBackup(); + final info = await client.getRoomKeysBackup(); if (info.algorithm != RoomKeysAlgorithmType.v1Curve25519AesSha2) { return false; } @@ -288,7 +288,7 @@ class KeyManager { key: client.userID, ); try { - await client.sendToDevice(deviceKeys, 'm.room_key', rawSession); + await client.sendToDeviceEncrypted(deviceKeys, 'm.room_key', rawSession); await storeOutboundGroupSession(roomId, sess); _outboundGroupSessions[roomId] = sess; } catch (e, s) { @@ -339,7 +339,7 @@ class KeyManager { final privateKey = base64.decode(await encryption.ssss.getCached(MEGOLM_KEY)); final decryption = olm.PkDecryption(); - final info = await client.api.getRoomKeysBackup(); + final info = await client.getRoomKeysBackup(); String backupPubKey; try { backupPubKey = decryption.init_with_private_key(privateKey); @@ -387,9 +387,9 @@ class KeyManager { } Future loadSingleKey(String roomId, String sessionId) async { - final info = await client.api.getRoomKeysBackup(); + final info = await client.getRoomKeysBackup(); final ret = - await client.api.getRoomKeysSingleKey(roomId, sessionId, info.version); + await client.getRoomKeysSingleKey(roomId, sessionId, info.version); final keys = RoomKeys.fromJson({ 'rooms': { roomId: { @@ -434,22 +434,22 @@ class KeyManager { 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, + final userList = await room.requestParticipants(); + await client.sendToDevicesOfUserIds( + userList.map((u) => u.id).toSet(), + 'm.room_key_request', + { + 'action': 'request', + 'body': { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': room.id, + 'sender_key': senderKey, + 'session_id': sessionId, }, - encrypted: false, - toUsers: await room.requestParticipants()); + 'request_id': requestId, + 'requesting_device_id': client.deviceID, + }, + ); outgoingShareRequests[request.requestId] = request; } catch (e, s) { Logs.error( @@ -568,15 +568,24 @@ class KeyManager { if (request.devices.isEmpty) { return; // no need to send any cancellation } + // Send with send-to-device messaging + final sendToDeviceMessage = { + 'action': 'request_cancellation', + 'request_id': request.requestId, + 'requesting_device_id': client.deviceID, + }; + var data = >>{}; + for (final device in request.devices) { + if (!data.containsKey(device.userId)) { + data[device.userId] = {}; + } + data[device.userId][device.deviceId] = sendToDeviceMessage; + } await client.sendToDevice( - request.devices, - 'm.room_key_request', - { - 'action': 'request_cancellation', - 'request_id': request.requestId, - 'requesting_device_id': client.deviceID, - }, - encrypted: false); + 'm.room_key_request', + client.generateUniqueTransactionId(), + data, + ); } else if (event.type == 'm.room_key') { if (event.encryptedContent == null) { return; // the event wasn't encrypted, this is a security risk; @@ -675,7 +684,7 @@ class RoomKeyRequest extends ToDeviceEvent { 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( + await keyManager.client.sendToDeviceEncrypted( [requestingDevice], 'm.forwarded_room_key', message, diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index a71bf32..6bb9493 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -183,7 +183,7 @@ class OlmManager { signJson(keysContent['device_keys'] as Map); } - final response = await client.api.uploadDeviceKeys( + final response = await client.uploadDeviceKeys( deviceKeys: uploadDeviceKeys ? MatrixDeviceKeys.fromJson(keysContent['device_keys']) : null, @@ -335,7 +335,7 @@ class OlmManager { return; } await startOutgoingOlmSessions([device]); - await client.sendToDevice([device], 'm.dummy', {}); + await client.sendToDeviceEncrypted([device], 'm.dummy', {}); } Future decryptToDeviceEvent(ToDeviceEvent event) async { @@ -383,7 +383,7 @@ class OlmManager { } final response = - await client.api.requestOneTimeKeys(requestingKeysFrom, timeout: 10000); + await client.requestOneTimeKeys(requestingKeysFrom, timeout: 10000); for (var userKeysEntry in response.oneTimeKeys.entries) { final userId = userKeysEntry.key; diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 49b35ef..fb53ae2 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -222,7 +222,7 @@ class SSSS { 'mac': encrypted.mac, }; // store the thing in your account data - await client.api.setAccountData(client.userID, type, content); + await client.setAccountData(client.userID, type, content); if (CACHE_TYPES.contains(type) && client.database != null) { // cache the thing await client.database @@ -271,7 +271,7 @@ class SSSS { devices: devices, ); pendingShareRequests[requestId] = request; - await client.sendToDevice(devices, 'm.secret.request', { + await client.sendToDeviceEncrypted(devices, 'm.secret.request', { 'action': 'request', 'requesting_device_id': client.deviceID, 'request_id': requestId, @@ -308,7 +308,7 @@ class SSSS { } // okay, all checks out...time to share this secret! Logs.info('[SSSS] Replying with secret for ${type}'); - await client.sendToDevice( + await client.sendToDeviceEncrypted( [device], 'm.secret.send', { diff --git a/lib/encryption/utils/key_verification.dart b/lib/encryption/utils/key_verification.dart index 8250068..4b89f06 100644 --- a/lib/encryption/utils/key_verification.dart +++ b/lib/encryption/utils/key_verification.dart @@ -571,7 +571,7 @@ class KeyVerification { } else { Logs.info( '[Key Verification] Sending to ${userId} device ${deviceId}...'); - await client.sendToDevice( + await client.sendToDeviceEncrypted( [client.userDeviceKeys[userId].deviceKeys[deviceId]], type, payload); } } diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart index ce528d2..50b66c0 100644 --- a/lib/matrix_api/matrix_api.dart +++ b/lib/matrix_api/matrix_api.dart @@ -1329,8 +1329,11 @@ class MatrixApi { /// This endpoint is used to send send-to-device events to a set of client devices. /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-sendtodevice-eventtype-txnid - Future sendToDevice(String eventType, String txnId, - Map>> messages) async { + Future sendToDevice( + String eventType, + String txnId, + Map>> messages, + ) async { await request( RequestType.PUT, '/client/r0/sendToDevice/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}', diff --git a/lib/src/client.dart b/lib/src/client.dart index 74d75c1..806f9a0 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -22,7 +22,6 @@ import 'dart:core'; import 'package:famedlysdk/encryption.dart'; import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/src/room.dart'; import 'package:famedlysdk/src/utils/device_keys_list.dart'; import 'package:famedlysdk/src/utils/logs.dart'; @@ -45,7 +44,7 @@ enum LoginState { logged, loggedOut } /// Represents a Matrix client to communicate with a /// [Matrix](https://matrix.org) homeserver and is the entry point for this /// SDK. -class Client { +class Client extends MatrixApi { int _id; int get id => _id; @@ -53,7 +52,8 @@ class Client { bool enableE2eeRecovery; - MatrixApi api; + @deprecated + MatrixApi get api => this; Encryption encryption; @@ -103,7 +103,7 @@ class Client { EventTypes.RoomCanonicalAlias, EventTypes.RoomTombstone, ]); - api = MatrixApi(httpClient: httpClient); + this.httpClient = httpClient; } /// The required name for this client. @@ -125,7 +125,7 @@ class Client { String _deviceName; /// Returns the current login state. - bool isLogged() => api.accessToken != null; + bool isLogged() => accessToken != null; /// A list of all rooms the user is participating or invited. List get rooms => _rooms; @@ -160,21 +160,6 @@ class Client { int _transactionCounter = 0; - @Deprecated('Use [api.request()] instead') - Future> jsonRequest( - {RequestType type, - String action, - dynamic data = '', - int timeout, - String contentType = 'application/json'}) => - api.request( - type, - action, - data: data, - timeout: timeout, - contentType: contentType, - ); - String generateUniqueTransactionId() { _transactionCounter++; return '${clientName}-${_transactionCounter}-${DateTime.now().millisecondsSinceEpoch}'; @@ -243,7 +228,7 @@ class Client { Future checkServer(dynamic serverUrl) async { try { if (serverUrl is Uri) { - api.homeserver = serverUrl; + homeserver = serverUrl; } else { // URLs allow to have whitespace surrounding them, see https://www.w3.org/TR/2011/WD-html5-20110525/urls.html // As we want to strip a trailing slash, though, we have to trim the url ourself @@ -253,9 +238,9 @@ class Client { if (serverUrl.endsWith('/')) { serverUrl = serverUrl.substring(0, serverUrl.length - 1); } - api.homeserver = Uri.parse(serverUrl); + homeserver = Uri.parse(serverUrl); } - final versions = await api.requestSupportedVersions(); + final versions = await requestSupportedVersions(); for (var i = 0; i < versions.versions.length; i++) { if (versions.versions[i] == 'r0.5.0' || @@ -266,7 +251,7 @@ class Client { } } - final loginTypes = await api.requestLoginTypes(); + final loginTypes = await requestLoginTypes(); if (loginTypes.flows.indexWhere((f) => f.type == 'm.login.password') == -1) { return false; @@ -274,7 +259,7 @@ class Client { return true; } catch (_) { - api.homeserver = null; + homeserver = null; rethrow; } } @@ -282,16 +267,17 @@ class Client { /// Checks to see if a username is available, and valid, for the server. /// Returns the fully-qualified Matrix user ID (MXID) that has been registered. /// You have to call [checkServer] first to set a homeserver. - Future register({ - String kind, + @override + Future register({ String username, String password, - Map auth, String deviceId, String initialDeviceDisplayName, bool inhibitLogin, + Map auth, + String kind, }) async { - final response = await api.register( + final response = await super.register( username: username, password: password, auth: auth, @@ -309,66 +295,62 @@ class Client { await connect( newToken: response.accessToken, newUserID: response.userId, - newHomeserver: api.homeserver, + newHomeserver: homeserver, newDeviceName: initialDeviceDisplayName ?? '', newDeviceID: response.deviceId); - return; + return response; } /// Handles the login and allows the client to call all APIs which require /// authentication. Returns false if the login was not successful. Throws /// MatrixException if login was not successful. /// You have to call [checkServer] first to set a homeserver. - Future login( - String username, - String password, { - String initialDeviceDisplayName, + @override + Future login({ + String type = 'm.login.password', + String userIdentifierType = 'm.id.user', + String user, + String medium, + String address, + String password, + String token, String deviceId, + String initialDeviceDisplayName, }) async { - var data = { - 'type': 'm.login.password', - 'user': username, - 'identifier': { - 'type': 'm.id.user', - 'user': username, - }, - 'password': password, - }; - if (deviceId != null) data['device_id'] = deviceId; - if (initialDeviceDisplayName != null) { - data['initial_device_display_name'] = initialDeviceDisplayName; - } - - final loginResp = await api.login( - type: 'm.login.password', - userIdentifierType: 'm.id.user', - user: username, + final loginResp = await super.login( + type: type, + userIdentifierType: userIdentifierType, + user: user, password: password, deviceId: deviceId, initialDeviceDisplayName: initialDeviceDisplayName, + medium: medium, + address: address, + token: token, ); // Connect if there is an access token in the response. if (loginResp.accessToken == null || loginResp.deviceId == null || loginResp.userId == null) { - throw 'Registered but token, device ID or user ID is null.'; + throw Exception('Registered but token, device ID or user ID is null.'); } await connect( newToken: loginResp.accessToken, newUserID: loginResp.userId, - newHomeserver: api.homeserver, + newHomeserver: homeserver, newDeviceName: initialDeviceDisplayName ?? '', newDeviceID: loginResp.deviceId, ); - return true; + return loginResp; } /// Sends a logout command to the homeserver and clears all local data, /// including all persistent data from the store. + @override Future logout() async { try { - await api.logout(); + await super.logout(); } catch (e, s) { Logs.error(e, s); rethrow; @@ -421,19 +403,19 @@ class Client { if (cache && _profileCache.containsKey(userId)) { return _profileCache[userId]; } - final profile = await api.requestProfile(userId); + final profile = await requestProfile(userId); _profileCache[userId] = profile; return profile; } Future> get archive async { var archiveList = []; - final sync = await api.sync( + final syncResp = await sync( filter: '{"room":{"include_leave":true,"timeline":{"limit":10}}}', timeout: 0, ); - if (sync.rooms.leave is Map) { - for (var entry in sync.rooms.leave.entries) { + if (syncResp.rooms.leave is Map) { + for (var entry in syncResp.rooms.leave.entries) { final id = entry.key; final room = entry.value; var leftRoom = Room( @@ -460,14 +442,10 @@ class Client { return archiveList; } - /// Changes the user's displayname. - Future setDisplayname(String displayname) => - api.setDisplayname(userID, displayname); - /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { - final uploadResp = await api.upload(file.bytes, file.name); - await api.setAvatarUrl(userID, Uri.parse(uploadResp)); + final uploadResp = await upload(file.bytes, file.name); + await setAvatarUrl(userID, Uri.parse(uploadResp)); return; } @@ -550,10 +528,6 @@ class Client { final StreamController onKeyVerificationRequest = StreamController.broadcast(); - /// Matrix synchronisation is done with https long polling. This needs a - /// timeout which is usually 30 seconds. - int syncTimeoutSec = 30; - /// How long should the app wait until it retrys the synchronisation after /// an error? int syncErrorTimeoutSec = 3; @@ -575,7 +549,7 @@ class Client { /// "type": "m.login.password", /// "user": "test", /// "password": "1234", - /// "initial_device_display_name": "Fluffy Matrix Client" + /// "initial_device_display_name": "Matrix Client" /// }); /// ``` /// @@ -604,8 +578,8 @@ class Client { final account = await database.getClient(clientName); if (account != null) { _id = account.clientId; - api.homeserver = Uri.parse(account.homeserverUrl); - api.accessToken = account.token; + homeserver = Uri.parse(account.homeserverUrl); + accessToken = account.token; _userID = account.userId; _deviceID = account.deviceId; _deviceName = account.deviceName; @@ -613,15 +587,15 @@ class Client { olmAccount = account.olmAccount; } } - api.accessToken = newToken ?? api.accessToken; - api.homeserver = newHomeserver ?? api.homeserver; + accessToken = newToken ?? accessToken; + homeserver = newHomeserver ?? homeserver; _userID = newUserID ?? _userID; _deviceID = newDeviceID ?? _deviceID; _deviceName = newDeviceName ?? _deviceName; prevBatch = newPrevBatch ?? prevBatch; olmAccount = newOlmAccount ?? olmAccount; - if (api.accessToken == null || api.homeserver == null || _userID == null) { + if (accessToken == null || homeserver == null || _userID == null) { // we aren't logged in encryption?.dispose(); encryption = null; @@ -636,8 +610,8 @@ class Client { if (database != null) { if (id != null) { await database.updateClient( - api.homeserver.toString(), - api.accessToken, + homeserver.toString(), + accessToken, _userID, _deviceID, _deviceName, @@ -648,8 +622,8 @@ class Client { } else { _id = await database.insertClient( clientName, - api.homeserver.toString(), - api.accessToken, + homeserver.toString(), + accessToken, _userID, _deviceID, _deviceName, @@ -666,7 +640,7 @@ class Client { onLoginStateChanged.add(LoginState.logged); Logs.success( - 'Successfully connected as ${userID.localpart} with ${api.homeserver.toString()}', + 'Successfully connected as ${userID.localpart} with ${homeserver.toString()}', ); return _sync(); @@ -680,8 +654,8 @@ class Client { /// Resets all settings and stops the synchronisation. void clear() { database?.clear(id); - _id = api.accessToken = - api.homeserver = _userID = _deviceID = _deviceName = prevBatch = null; + _id = accessToken = + homeserver = _userID = _deviceID = _deviceName = prevBatch = null; _rooms = []; encryption?.dispose(); encryption = null; @@ -694,13 +668,11 @@ class Client { Future _sync() async { if (isLogged() == false || _disposed) return; try { - _syncRequest = api - .sync( + _syncRequest = sync( filter: syncFilters, since: prevBatch, timeout: prevBatch != null ? 30000 : null, - ) - .catchError((e) { + ).catchError((e) { _lastSyncError = e; return null; }); @@ -1197,8 +1169,7 @@ class Client { if (outdatedLists.isNotEmpty) { // Request the missing device key lists from the server. - final response = - await api.requestDeviceKeys(outdatedLists, timeout: 10000); + final response = await requestDeviceKeys(outdatedLists, timeout: 10000); for (final rawDeviceKeyListEntry in response.deviceKeys.entries) { final userId = rawDeviceKeyListEntry.key; @@ -1347,17 +1318,35 @@ class Client { } } + /// Send an (unencrypted) to device [message] of a specific [eventType] to all + /// devices of a set of [users]. + Future sendToDevicesOfUserIds( + Set users, + String eventType, + Map message, { + String messageId, + }) async { + // Send with send-to-device messaging + var data = >>{}; + for (var user in users) { + data[user] = {}; + data[user]['*'] = message; + } + await sendToDevice( + eventType, messageId ?? generateUniqueTransactionId(), data); + return; + } + /// Sends an encrypted [message] of this [type] to these [deviceKeys]. To send /// the request to all devices of the current user, pass an empty list to [deviceKeys]. - Future sendToDevice( + Future sendToDeviceEncrypted( List deviceKeys, - String type, + String eventType, Map message, { - bool encrypted = true, - List toUsers, + String messageId, bool onlyVerified = false, }) async { - if (encrypted && !encryptionEnabled) return; + if (!encryptionEnabled) return; // Don't send this message to blocked devices, and if specified onlyVerified // then only send it to verified devices if (deviceKeys.isNotEmpty) { @@ -1368,36 +1357,13 @@ class Client { if (deviceKeys.isEmpty) return; } - var sendToDeviceMessage = message; - // Send with send-to-device messaging var data = >>{}; - if (deviceKeys.isEmpty) { - if (toUsers == null) { - data[userID] = {}; - data[userID]['*'] = sendToDeviceMessage; - } else { - for (var user in toUsers) { - data[user.id] = {}; - data[user.id]['*'] = sendToDeviceMessage; - } - } - } else { - if (encrypted) { - data = - await encryption.encryptToDeviceMessage(deviceKeys, type, message); - } else { - for (final device in deviceKeys) { - if (!data.containsKey(device.userId)) { - data[device.userId] = {}; - } - data[device.userId][device.deviceId] = sendToDeviceMessage; - } - } - } - if (encrypted) type = EventTypes.Encrypted; - final messageID = generateUniqueTransactionId(); - await api.sendToDevice(type, messageID, data); + data = + await encryption.encryptToDeviceMessage(deviceKeys, eventType, message); + eventType = EventTypes.Encrypted; + await sendToDevice( + eventType, messageId ?? generateUniqueTransactionId(), data); } /// Whether all push notifications are muted using the [.m.rule.master] @@ -1422,7 +1388,7 @@ class Client { } Future setMuteAllPushNotifications(bool muted) async { - await api.enablePushRule( + await enablePushRule( 'global', PushRuleKind.override, '.m.rule.master', @@ -1432,6 +1398,7 @@ class Client { } /// Changes the password. You should either set oldPasswort or another authentication flow. + @override Future changePassword(String newPassword, {String oldPassword, Map auth}) async { try { @@ -1442,7 +1409,7 @@ class Client { 'password': oldPassword, }; } - await api.changePassword(newPassword, auth: auth); + await super.changePassword(newPassword, auth: auth); } on MatrixException catch (matrixException) { if (!matrixException.requireAdditionalAuthentication) { rethrow; diff --git a/lib/src/room.dart b/lib/src/room.dart index b085d50..f8d25c1 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -374,21 +374,21 @@ class Room { /// Call the Matrix API to change the name of this room. Returns the event ID of the /// new m.room.name event. - Future setName(String newName) => client.api.sendState( + Future setName(String newName) => client.sendState( id, EventTypes.RoomName, {'name': newName}, ); /// Call the Matrix API to change the topic of this room. - Future setDescription(String newName) => client.api.sendState( + Future setDescription(String newName) => client.sendState( id, EventTypes.RoomTopic, {'topic': newName}, ); /// Add a tag to the room. - Future addTag(String tag, {double order}) => client.api.addRoomTag( + Future addTag(String tag, {double order}) => client.addRoomTag( client.userID, id, tag, @@ -396,7 +396,7 @@ class Room { ); /// Removes a tag from the room. - Future removeTag(String tag) => client.api.removeRoomTag( + Future removeTag(String tag) => client.removeRoomTag( client.userID, id, tag, @@ -423,7 +423,7 @@ class Room { /// Call the Matrix API to change the pinned events of this room. Future setPinnedEvents(List pinnedEventIds) => - client.api.sendState( + client.sendState( id, EventTypes.RoomPinnedEvents, {'pinned': pinnedEventIds}, @@ -565,13 +565,13 @@ class Room { uploadThumbnail = encryptedThumbnail.toMatrixFile(); } } - final uploadResp = await client.api.upload( + final uploadResp = await client.upload( uploadFile.bytes, uploadFile.name, contentType: uploadFile.mimeType, ); final thumbnailUploadResp = uploadThumbnail != null - ? await client.api.upload( + ? await client.upload( uploadThumbnail.bytes, uploadThumbnail.name, contentType: uploadThumbnail.mimeType, @@ -705,7 +705,7 @@ class Room { ? await client.encryption .encryptGroupMessagePayload(id, content, type: type) : content; - final res = await client.api.sendMessage( + final res = await client.sendMessage( id, sendType, messageID, @@ -731,7 +731,7 @@ class Room { /// automatically be set. Future join() async { try { - await client.api.joinRoom(id); + await client.joinRoom(id); final invitation = getState(EventTypes.RoomMember, client.userID); if (invitation != null && invitation.content['is_direct'] is bool && @@ -757,25 +757,25 @@ class Room { /// chat, this will be removed too. Future leave() async { if (directChatMatrixID != '') await removeFromDirectChat(); - await client.api.leaveRoom(id); + await client.leaveRoom(id); return; } /// Call the Matrix API to forget this room if you already left it. Future forget() async { await client.database?.forgetRoom(client.id, id); - await client.api.forgetRoom(id); + await client.forgetRoom(id); return; } /// Call the Matrix API to kick a user from this room. - Future kick(String userID) => client.api.kickFromRoom(id, userID); + Future kick(String userID) => client.kickFromRoom(id, userID); /// Call the Matrix API to ban a user from this room. - Future ban(String userID) => client.api.banFromRoom(id, userID); + Future ban(String userID) => client.banFromRoom(id, userID); /// Call the Matrix API to unban a banned user from this room. - Future unban(String userID) => client.api.unbanInRoom(id, userID); + Future unban(String userID) => client.unbanInRoom(id, userID); /// Set the power level of the user with the [userID] to the value [power]. /// Returns the event ID of the new state event. If there is no known @@ -787,7 +787,7 @@ class Room { if (powerMap['users'] == null) powerMap['users'] = {}; powerMap['users'][userID] = power; - return await client.api.sendState( + return await client.sendState( id, EventTypes.RoomPowerLevels, powerMap, @@ -795,14 +795,14 @@ class Room { } /// Call the Matrix API to invite a user to this room. - Future invite(String userID) => client.api.inviteToRoom(id, userID); + Future invite(String userID) => client.inviteToRoom(id, userID); /// Request more previous events from the server. [historyCount] defines how much events should /// be received maximum. When the request is answered, [onHistoryReceived] will be triggered **before** /// the historical events will be published in the onEvent stream. Future requestHistory( {int historyCount = DefaultHistoryCount, onHistoryReceived}) async { - final resp = await client.api.requestMessages( + final resp = await client.requestMessages( id, prev_batch, Direction.b, @@ -853,7 +853,7 @@ class Room { directChats[userID] = [id]; } - await client.api.setAccountData( + await client.setAccountData( client.userID, 'm.direct', directChats, @@ -871,7 +871,7 @@ class Room { return; } // Nothing to do here - await client.api.setRoomAccountData( + await client.setRoomAccountData( client.userID, id, 'm.direct', @@ -884,7 +884,7 @@ class Room { Future sendReadReceipt(String eventID) async { notificationCount = 0; await client.database?.resetNotificationCount(client.id, id); - await client.api.sendReadMarker( + await client.sendReadMarker( id, eventID, readReceiptLocationEventId: eventID, @@ -1017,7 +1017,7 @@ class Room { } } if (participantListComplete) return getParticipants(); - final matrixEvents = await client.api.requestMembers(id); + final matrixEvents = await client.requestMembers(id); final users = matrixEvents.map((e) => Event.fromMatrixEvent(e, this).asUser).toList(); for (final user in users) { @@ -1080,7 +1080,7 @@ class Room { if (mxID == null || !_requestingMatrixIds.add(mxID)) return null; Map resp; try { - resp = await client.api.requestStateContent( + resp = await client.requestStateContent( id, EventTypes.RoomMember, mxID, @@ -1093,7 +1093,7 @@ class Room { } if (resp == null && requestProfile) { try { - final profile = await client.api.requestProfile(mxID); + final profile = await client.requestProfile(mxID); resp = { 'displayname': profile.displayname, 'avatar_url': profile.avatarUrl, @@ -1135,7 +1135,7 @@ class Room { /// Searches for the event on the server. Returns null if not found. Future getEventById(String eventID) async { - final matrixEvent = await client.api.requestEvent(id, eventID); + final matrixEvent = await client.requestEvent(id, eventID); return Event.fromMatrixEvent(matrixEvent, this); } @@ -1169,8 +1169,8 @@ class Room { /// Uploads a new user avatar for this room. Returns the event ID of the new /// m.room.avatar event. Future setAvatar(MatrixFile file) async { - final uploadResp = await client.api.upload(file.bytes, file.name); - return await client.api.sendState( + final uploadResp = await client.upload(file.bytes, file.name); + return await client.sendState( id, EventTypes.RoomAvatar, {'url': uploadResp}, @@ -1267,23 +1267,23 @@ class Room { // All push notifications should be sent to the user case PushRuleState.notify: if (pushRuleState == PushRuleState.dont_notify) { - await client.api.deletePushRule('global', PushRuleKind.override, id); + await client.deletePushRule('global', PushRuleKind.override, id); } else if (pushRuleState == PushRuleState.mentions_only) { - await client.api.deletePushRule('global', PushRuleKind.room, id); + await client.deletePushRule('global', PushRuleKind.room, id); } break; // Only when someone mentions the user, a push notification should be sent case PushRuleState.mentions_only: if (pushRuleState == PushRuleState.dont_notify) { - await client.api.deletePushRule('global', PushRuleKind.override, id); - await client.api.setPushRule( + await client.deletePushRule('global', PushRuleKind.override, id); + await client.setPushRule( 'global', PushRuleKind.room, id, [PushRuleAction.dont_notify], ); } else if (pushRuleState == PushRuleState.notify) { - await client.api.setPushRule( + await client.setPushRule( 'global', PushRuleKind.room, id, @@ -1294,9 +1294,9 @@ class Room { // No push notification should be ever sent for this room. case PushRuleState.dont_notify: if (pushRuleState == PushRuleState.mentions_only) { - await client.api.deletePushRule('global', PushRuleKind.room, id); + await client.deletePushRule('global', PushRuleKind.room, id); } - await client.api.setPushRule( + await client.setPushRule( 'global', PushRuleKind.override, id, @@ -1322,7 +1322,7 @@ class Room { } var data = {}; if (reason != null) data['reason'] = reason; - return await client.api.redact( + return await client.redact( id, eventId, messageID, @@ -1335,7 +1335,7 @@ class Room { 'typing': isTyping, }; if (timeout != null) data['timeout'] = timeout; - return client.api.sendTypingNotification(client.userID, id, isTyping); + return client.sendTypingNotification(client.userID, id, isTyping); } /// This is sent by the caller when they wish to establish a call. @@ -1349,7 +1349,7 @@ class Room { {String type = 'offer', int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - return await client.api.sendMessage( + return await client.sendMessage( id, EventTypes.CallInvite, txid, @@ -1387,7 +1387,7 @@ class Room { String txid, }) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - return await client.api.sendMessage( + return await client.sendMessage( id, EventTypes.CallCandidates, txid, @@ -1407,7 +1407,7 @@ class Room { Future answerCall(String callId, String sdp, {String type = 'answer', int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - return await client.api.sendMessage( + return await client.sendMessage( id, EventTypes.CallAnswer, txid, @@ -1425,7 +1425,7 @@ class Room { Future hangupCall(String callId, {int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - return await client.api.sendMessage( + return await client.sendMessage( id, EventTypes.CallHangup, txid, @@ -1461,7 +1461,7 @@ class Room { /// Changes the join rules. You should check first if the user is able to change it. Future setJoinRules(JoinRules joinRules) async { - await client.api.sendState( + await client.sendState( id, EventTypes.RoomJoinRules, { @@ -1486,7 +1486,7 @@ class Room { /// Changes the guest access. You should check first if the user is able to change it. Future setGuestAccess(GuestAccess guestAccess) async { - await client.api.sendState( + await client.sendState( id, EventTypes.GuestAccess, { @@ -1512,7 +1512,7 @@ class Room { /// Changes the history visibility. You should check first if the user is able to change it. Future setHistoryVisibility(HistoryVisibility historyVisibility) async { - await client.api.sendState( + await client.sendState( id, EventTypes.HistoryVisibility, { @@ -1539,7 +1539,7 @@ class Room { Future enableEncryption({int algorithmIndex = 0}) async { if (encrypted) throw ('Encryption is already enabled!'); final algorithm = Client.supportedGroupEncryptionAlgorithms[algorithmIndex]; - await client.api.sendState( + await client.sendState( id, EventTypes.Encryption, { diff --git a/lib/src/user.dart b/lib/src/user.dart index 3efedf4..ea1ce42 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -146,7 +146,7 @@ class User extends Event { if (roomID != null) return roomID; // Start a new direct chat - final newRoomID = await room.client.api.createRoom( + final newRoomID = await room.client.createRoom( invite: [id], isDirect: true, preset: CreateRoomPreset.trusted_private_chat, diff --git a/lib/src/utils/uri_extension.dart b/lib/src/utils/uri_extension.dart index edecdcf..804e385 100644 --- a/lib/src/utils/uri_extension.dart +++ b/lib/src/utils/uri_extension.dart @@ -22,8 +22,8 @@ import 'dart:core'; extension MxcUriExtension on Uri { /// Returns a download Link to this content. String getDownloadLink(Client matrix) => isScheme('mxc') - ? matrix.api.homeserver != null - ? '${matrix.api.homeserver.toString()}/_matrix/media/r0/download/$host$path' + ? matrix.homeserver != null + ? '${matrix.homeserver.toString()}/_matrix/media/r0/download/$host$path' : '' : toString(); @@ -36,8 +36,8 @@ extension MxcUriExtension on Uri { final methodStr = method.toString().split('.').last; width = width.round(); height = height.round(); - return matrix.api.homeserver != null - ? '${matrix.api.homeserver.toString()}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr' + return matrix.homeserver != null + ? '${matrix.homeserver.toString()}/_matrix/media/r0/thumbnail/$host$path?width=$width&height=$height&method=$methodStr' : ''; } } diff --git a/test/client_test.dart b/test/client_test.dart index 8d0242f..2fa4178 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -46,7 +46,7 @@ void main() { const fingerprintKey = 'gjL//fyaFHADt9KBADGag8g7F8Up78B/K1zXeiEPLJo'; /// All Tests related to the Login - group('FluffyMatrix', () { + group('Client', () { /// Check if all Elements get created matrix = Client('testclient', httpClient: FakeMatrixApi()); @@ -74,7 +74,7 @@ void main() { accountDataCounter++; }); - expect(matrix.api.homeserver, null); + expect(matrix.homeserver, null); try { await matrix.checkServer('https://fakeserver.wrongaddress'); @@ -82,17 +82,9 @@ void main() { expect(exception != null, true); } await matrix.checkServer('https://fakeserver.notexisting'); - expect( - matrix.api.homeserver.toString(), 'https://fakeserver.notexisting'); + expect(matrix.homeserver.toString(), 'https://fakeserver.notexisting'); - final resp = await matrix.api.login( - type: 'm.login.password', - user: 'test', - password: '1234', - initialDeviceDisplayName: 'Fluffy Matrix Client', - ); - - final available = await matrix.api.usernameAvailable('testuser'); + final available = await matrix.usernameAvailable('testuser'); expect(available, true); var loginStateFuture = matrix.onLoginStateChanged.stream.first; @@ -100,21 +92,16 @@ void main() { var syncFuture = matrix.onSync.stream.first; matrix.connect( - newToken: resp.accessToken, - newUserID: resp.userId, - newHomeserver: matrix.api.homeserver, + newToken: 'abcd', + newUserID: '@test:fakeServer.notExisting', + newHomeserver: matrix.homeserver, newDeviceName: 'Text Matrix Client', - newDeviceID: resp.deviceId, + newDeviceID: 'GHTYAJCE', newOlmAccount: pickledOlmAccount, ); await Future.delayed(Duration(milliseconds: 50)); - expect(matrix.api.accessToken == resp.accessToken, true); - expect(matrix.deviceName == 'Text Matrix Client', true); - expect(matrix.deviceID == resp.deviceId, true); - expect(matrix.userID == resp.userId, true); - var loginState = await loginStateFuture; var firstSync = await firstSyncFuture; var sync = await syncFuture; @@ -208,14 +195,11 @@ void main() { }); test('Logout', () async { - await matrix.api.logout(); - var loginStateFuture = matrix.onLoginStateChanged.stream.first; + await matrix.logout(); - matrix.clear(); - - expect(matrix.api.accessToken == null, true); - expect(matrix.api.homeserver == null, true); + expect(matrix.accessToken == null, true); + expect(matrix.homeserver == null, true); expect(matrix.userID == null, true); expect(matrix.deviceID == null, true); expect(matrix.deviceName == null, true); @@ -330,10 +314,10 @@ void main() { final checkResp = await matrix.checkServer('https://fakeServer.notExisting'); - final loginResp = await matrix.login('test', '1234'); + final loginResp = await matrix.login(user: 'test', password: '1234'); expect(checkResp, true); - expect(loginResp, true); + expect(loginResp != null, true); }); test('setAvatar', () async { @@ -386,8 +370,8 @@ void main() { } } }, matrix); - test('sendToDevice', () async { - await matrix.sendToDevice( + test('sendToDeviceEncrypted', () async { + await matrix.sendToDeviceEncrypted( [deviceKeys], 'm.message', { @@ -420,9 +404,9 @@ void main() { await Future.delayed(Duration(milliseconds: 100)); expect(client2.isLogged(), true); - expect(client2.api.accessToken, client1.api.accessToken); + expect(client2.accessToken, client1.accessToken); expect(client2.userID, client1.userID); - expect(client2.api.homeserver, client1.api.homeserver); + expect(client2.homeserver, client1.homeserver); expect(client2.deviceID, client1.deviceID); expect(client2.deviceName, client1.deviceName); if (client2.encryptionEnabled) { diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index b98ef22..4fbde05 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -54,7 +54,7 @@ void main() { otherClient.connect( newToken: 'abc', newUserID: '@othertest:fakeServer.notExisting', - newHomeserver: otherClient.api.homeserver, + newHomeserver: otherClient.homeserver, newDeviceName: 'Text Matrix Client', newDeviceID: 'FOXDEVICE', newOlmAccount: otherPickledOlmAccount, diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 5ae9689..2612207 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -89,7 +89,7 @@ void main() { client2.connect( newToken: 'abc', newUserID: '@othertest:fakeServer.notExisting', - newHomeserver: client2.api.homeserver, + newHomeserver: client2.homeserver, newDeviceName: 'Text Matrix Client', newDeviceID: 'FOXDEVICE', newOlmAccount: otherPickledOlmAccount, diff --git a/test/event_test.dart b/test/event_test.dart index e57be26..f40f8ec 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -246,7 +246,7 @@ void main() { test('sendAgain', () async { var matrix = Client('testclient', httpClient: FakeMatrixApi()); await matrix.checkServer('https://fakeServer.notExisting'); - await matrix.login('test', '1234'); + await matrix.login(user: 'test', password: '1234'); var event = Event.fromJson( jsonObj, Room(id: '!1234:example.com', client: matrix)); @@ -262,7 +262,7 @@ void main() { test('requestKey', () async { var matrix = Client('testclient', httpClient: FakeMatrixApi()); await matrix.checkServer('https://fakeServer.notExisting'); - await matrix.login('test', '1234'); + await matrix.login(user: 'test', password: '1234'); var event = Event.fromJson( jsonObj, Room(id: '!1234:example.com', client: matrix)); diff --git a/test/fake_client.dart b/test/fake_client.dart index 7b1a94a..5f30487 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -32,18 +32,12 @@ Future getClient() async { final client = Client('testclient', 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, + newToken: 'abcd', + newUserID: '@test:fakeServer.notExisting', + newHomeserver: client.homeserver, newDeviceName: 'Text Matrix Client', - newDeviceID: resp.deviceId, + newDeviceID: 'GHTYAJCE', newOlmAccount: pickledOlmAccount, ); await Future.delayed(Duration(milliseconds: 10)); diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index 760dfcf..2dec135 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -82,6 +82,10 @@ class FakeMatrixApi extends MockClient { action.contains( '/client/r0/rooms/!1234%3AfakeServer.notExisting/send/')) { res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'}; + } else if (action.contains('/client/r0/sync')) { + res = { + 'next_batch': DateTime.now().millisecondsSinceEpoch.toString + }; } else { res = { 'errcode': 'M_UNRECOGNIZED', diff --git a/test/mxc_uri_extension_test.dart b/test/mxc_uri_extension_test.dart index 798854c..30e6f84 100644 --- a/test/mxc_uri_extension_test.dart +++ b/test/mxc_uri_extension_test.dart @@ -33,13 +33,13 @@ void main() { expect(content.isScheme('mxc'), true); expect(content.getDownloadLink(client), - '${client.api.homeserver.toString()}/_matrix/media/r0/download/exampleserver.abc/abcdefghijklmn'); + '${client.homeserver.toString()}/_matrix/media/r0/download/exampleserver.abc/abcdefghijklmn'); expect(content.getThumbnail(client, width: 50, height: 50), - '${client.api.homeserver.toString()}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop'); + '${client.homeserver.toString()}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop'); expect( content.getThumbnail(client, width: 50, height: 50, method: ThumbnailMethod.scale), - '${client.api.homeserver.toString()}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale'); + '${client.homeserver.toString()}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale'); }); }); } diff --git a/test/user_test.dart b/test/user_test.dart index 35d35d6..0e7de24 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -102,7 +102,7 @@ void main() { }); test('startDirectChat', () async { await client.checkServer('https://fakeserver.notexisting'); - await client.login('test', '1234'); + await client.login(user: 'test', password: '1234'); await user1.startDirectChat(); }); test('getPresence', () async { diff --git a/test_driver/famedlysdk_test.dart b/test_driver/famedlysdk_test.dart index 61f3cdc..f2f3988 100644 --- a/test_driver/famedlysdk_test.dart +++ b/test_driver/famedlysdk_test.dart @@ -22,14 +22,14 @@ void test() async { var testClientA = Client('TestClientA'); testClientA.database = getDatabase(); await testClientA.checkServer(homeserver); - await testClientA.login(testUserA, testPasswordA); + await testClientA.login(user: testUserA, password: testPasswordA); assert(testClientA.encryptionEnabled); Logs.success('++++ Login $testUserB ++++'); var testClientB = Client('TestClientB'); testClientB.database = getDatabase(); await testClientB.checkServer(homeserver); - await testClientB.login(testUserB, testPasswordA); + await testClientB.login(user: testUserB, password: testPasswordA); assert(testClientB.encryptionEnabled); Logs.success('++++ ($testUserA) Leave all rooms ++++'); @@ -72,7 +72,7 @@ void test() async { .userDeviceKeys[testUserB].deviceKeys[testClientB.deviceID].blocked); Logs.success('++++ ($testUserA) Create room and invite $testUserB ++++'); - await testClientA.api.createRoom(invite: [testUserB]); + await testClientA.createRoom(invite: [testUserB]); await Future.delayed(Duration(seconds: 1)); var room = testClientA.rooms.first; assert(room != null); @@ -217,7 +217,7 @@ void test() async { Logs.success('++++ Login $testUserB in another client ++++'); var testClientC = Client('TestClientC', database: getDatabase()); await testClientC.checkServer(homeserver); - await testClientC.login(testUserB, testPasswordA); + await testClientC.login(user: testUserB, password: testPasswordA); await Future.delayed(Duration(seconds: 3)); Logs.success( @@ -346,8 +346,8 @@ void test() async { await Future.delayed(Duration(seconds: 1)); await testClientA.dispose(); await testClientB.dispose(); - await testClientA.api.logoutAll(); - await testClientB.api.logoutAll(); + await testClientA.logoutAll(); + await testClientB.logoutAll(); testClientA = null; testClientB = null; return;