diff --git a/analysis_options.yaml b/analysis_options.yaml index b7c304f..c0e32f0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,5 +5,7 @@ linter: - camel_case_types analyzer: + errors: + todo: ignore # exclude: # - path/to/excluded/files/** \ No newline at end of file diff --git a/lib/famedlysdk.dart b/lib/famedlysdk.dart index 94a0f15..da8813d 100644 --- a/lib/famedlysdk.dart +++ b/lib/famedlysdk.dart @@ -1,55 +1,39 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ 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; diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart new file mode 100644 index 0000000..d364cf8 --- /dev/null +++ b/lib/matrix_api.dart @@ -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 . + */ + +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'; diff --git a/lib/matrix_api/matrix_api.dart b/lib/matrix_api/matrix_api.dart new file mode 100644 index 0000000..89edcc2 --- /dev/null +++ b/lib/matrix_api/matrix_api.dart @@ -0,0 +1,1987 @@ +/* + * 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 . + */ + +import 'dart:async'; +import 'dart:convert'; +import 'package:famedlysdk/matrix_api/model/filter.dart'; +import 'package:famedlysdk/matrix_api/model/keys_query_response.dart'; +import 'package:famedlysdk/matrix_api/model/login_types.dart'; +import 'package:famedlysdk/matrix_api/model/notifications_query_response.dart'; +import 'package:famedlysdk/matrix_api/model/open_graph_data.dart'; +import 'package:famedlysdk/matrix_api/model/request_token_response.dart'; +import 'package:famedlysdk/matrix_api/model/profile.dart'; +import 'package:famedlysdk/matrix_api/model/server_capabilities.dart'; +import 'package:famedlysdk/matrix_api/model/supported_versions.dart'; +import 'package:famedlysdk/matrix_api/model/sync_update.dart'; +import 'package:famedlysdk/matrix_api/model/third_party_location.dart'; +import 'package:famedlysdk/matrix_api/model/timeline_history_response.dart'; +import 'package:famedlysdk/matrix_api/model/user_search_result.dart'; +import 'package:http/http.dart' as http; +import 'package:mime_type/mime_type.dart'; +import 'package:moor/moor.dart'; + +import 'model/device.dart'; +import 'model/matrix_device_keys.dart'; +import 'model/matrix_event.dart'; +import 'model/event_context.dart'; +import 'model/events_sync_update.dart'; +import 'model/login_response.dart'; +import 'model/matrix_exception.dart'; +import 'model/one_time_keys_claim_response.dart'; +import 'model/open_id_credentials.dart'; +import 'model/presence_content.dart'; +import 'model/public_rooms_response.dart'; +import 'model/push_rule_set.dart'; +import 'model/pusher.dart'; +import 'model/room_alias_informations.dart'; +import 'model/supported_protocol.dart'; +import 'model/tag.dart'; +import 'model/third_party_identifier.dart'; +import 'model/third_party_user.dart'; +import 'model/turn_server_credentials.dart'; +import 'model/well_known_informations.dart'; +import 'model/who_is_info.dart'; + +enum RequestType { GET, POST, PUT, DELETE } +enum IdServerUnbindResult { success, no_success } +enum ThirdPartyIdentifierMedium { email, msisdn } +enum Membership { join, invite, leave, ban } +enum Direction { b, f } +enum Visibility { public, private } +enum CreateRoomPreset { private_chat, public_chat, trusted_private_chat } + +class MatrixApi { + /// The homeserver this client is communicating with. + Uri homeserver; + + /// This is the access token for the matrix client. When it is undefined, then + /// the user needs to sign in first. + String accessToken; + + /// Matrix synchronisation is done with https long polling. This needs a + /// timeout which is usually 30 seconds. + int syncTimeoutSec; + + /// Whether debug prints should be displayed. + final bool debug; + + http.Client httpClient = http.Client(); + + bool get _testMode => + homeserver.toString() == 'https://fakeserver.notexisting'; + + int _timeoutFactor = 1; + + MatrixApi({ + this.homeserver, + this.accessToken, + this.debug = false, + http.Client httpClient, + this.syncTimeoutSec = 30, + }) { + if (httpClient != null) { + this.httpClient = httpClient; + } + } + + /// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.6.0.html). + /// + /// Throws: TimeoutException, FormatException, MatrixException + /// + /// You must first set [this.homeserver] and for some endpoints also + /// [this.accessToken] before you can use this! For example to send a + /// message to a Matrix room with the id '!fjd823j:example.com' you call: + /// ``` + /// final resp = await request( + /// RequestType.PUT, + /// '/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId', + /// data: { + /// 'msgtype': 'm.text', + /// 'body': 'hello' + /// } + /// ); + /// ``` + /// + Future> request(RequestType type, String action, + {dynamic data = '', + int timeout, + String contentType = 'application/json'}) async { + if (homeserver == null) { + throw ('No homeserver specified.'); + } + timeout ??= (_timeoutFactor * syncTimeoutSec) + 5; + dynamic json; + if (data is Map) data.removeWhere((k, v) => v == null); + (!(data is String)) ? json = jsonEncode(data) : json = data; + if (data is List || action.startsWith('/media/r0/upload')) json = data; + + final url = '${homeserver.toString()}/_matrix${action}'; + + var headers = {}; + if (type == RequestType.PUT || type == RequestType.POST) { + headers['Content-Type'] = contentType; + } + if (accessToken != null) { + headers['Authorization'] = 'Bearer ${accessToken}'; + } + + if (debug) { + print( + "[REQUEST ${type.toString().split('.').last}] $action, Data: ${jsonEncode(data)}"); + } + + http.Response resp; + var jsonResp = {}; + try { + switch (type.toString().split('.').last) { + case 'GET': + resp = await httpClient.get(url, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'POST': + resp = + await httpClient.post(url, body: json, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'PUT': + resp = + await httpClient.put(url, body: json, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + case 'DELETE': + resp = await httpClient.delete(url, headers: headers).timeout( + Duration(seconds: timeout), + ); + break; + } + var jsonString = String.fromCharCodes(resp.body.runes); + if (jsonString.startsWith('[') && jsonString.endsWith(']')) { + jsonString = '\{"chunk":$jsonString\}'; + } + jsonResp = jsonDecode(jsonString) + as Map; // May throw FormatException + + if (resp.statusCode >= 400 && resp.statusCode < 500) { + // The server has responsed with an matrix related error. + var exception = MatrixException(resp); + + throw exception; + } + + if (debug) print('[RESPONSE] ${jsonResp.toString()}'); + _timeoutFactor = 1; + } on TimeoutException catch (_) { + _timeoutFactor *= 2; + rethrow; + } catch (_) { + rethrow; + } + + return jsonResp; + } + + /// Gets the versions of the specification supported by the server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-versions + Future requestSupportedVersions() async { + final response = await request( + RequestType.GET, + '/client/versions', + ); + return SupportedVersions.fromJson(response); + } + + /// Gets discovery information about the domain. The file may include additional keys. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-well-known-matrix-client + Future requestWellKnownInformations() async { + final response = await httpClient + .get('${homeserver.toString()}/.well-known/matrix/client'); + final rawJson = json.decode(response.body); + return WellKnownInformations.fromJson(rawJson); + } + + Future requestLoginTypes() async { + final response = await request( + RequestType.GET, + '/client/r0/login', + ); + return LoginTypes.fromJson(response); + } + + /// Authenticates the user, and issues an access token they can use to authorize themself in subsequent requests. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login + Future login({ + String type = 'm.login.password', + String userIdentifierType, + String user, + String medium, + String address, + String password, + String token, + String deviceId, + String initialDeviceDisplayName, + }) async { + final response = await request(RequestType.POST, '/client/r0/login', data: { + 'type': type, + if (userIdentifierType != null) + 'identifier': { + 'type': userIdentifierType, + if (user != null) 'user': user, + }, + if (user != null) 'user': user, + if (medium != null) 'medium': medium, + if (address != null) 'address': address, + if (password != null) 'password': password, + if (deviceId != null) 'device_id': deviceId, + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + }); + return LoginResponse.fromJson(response); + } + + /// Invalidates an existing access token, so that it can no longer be used for authorization. + /// The device associated with the access token is also deleted. Device keys for the device + /// are deleted alongside the device. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout + Future logout() async { + await request( + RequestType.POST, + '/client/r0/logout', + ); + return; + } + + /// Invalidates all access tokens for a user, so that they can no longer be used + /// for authorization. This includes the access token that made this request. All + /// devices for the user are also deleted. Device keys for the device are + /// deleted alongside the device. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all + Future logoutAll() async { + await request( + RequestType.POST, + '/client/r0/logout/all', + ); + return; + } + + Future register({ + String username, + String password, + String deviceId, + String initialDeviceDisplayName, + bool inhibitLogin, + Map auth, + String kind, + }) async { + var action = '/client/r0/register'; + if (kind != null) action += '?kind=${Uri.encodeQueryComponent(kind)}'; + final response = await request(RequestType.POST, action, data: { + if (username != null) 'username': username, + if (password != null) 'password': password, + if (deviceId != null) 'device_id': deviceId, + if (initialDeviceDisplayName != null) + 'initial_device_display_name': initialDeviceDisplayName, + if (inhibitLogin != null) 'inhibit_login': inhibitLogin, + if (auth != null) 'auth': auth, + }); + return LoginResponse.fromJson(response); + } + + /// The homeserver must check that the given email address is not already associated + /// with an account on this homeserver. The homeserver should validate the email + /// itself, either by sending a validation email itself or by using a service it + /// has control over. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-email-requesttoken + Future requestEmailToken( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/register/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// The homeserver must check that the given phone number is not already associated with an + /// account on this homeserver. The homeserver should validate the phone number itself, + /// either by sending a validation message itself or by using a service it has control over. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-msisdn-requesttoken + Future requestMsisdnToken( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/register/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// Changes the password for an account on this homeserver. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password + Future changePassword( + String newPassword, { + Map auth, + }) async { + await request(RequestType.POST, '/client/r0/account/password', data: { + 'new_password': newPassword, + if (auth != null) 'auth': auth, + }); + return; + } + + /// The homeserver must check that the given email address is associated with + /// an account on this homeserver. This API should be used to request + /// validation tokens when authenticating for the /account/password endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password-email-requesttoken + Future resetPasswordUsingEmail( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/password/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// The homeserver must check that the given phone number is associated with + /// an account on this homeserver. This API should be used to request validation + /// tokens when authenticating for the /account/password endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-password-msisdn-requesttoken + Future resetPasswordUsingMsisdn( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/password/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + Future deactivateAccount({ + String idServer, + Map auth, + }) async { + final response = + await request(RequestType.POST, '/client/r0/account/deactivate', data: { + if (idServer != null) 'id_server': idServer, + if (auth != null) 'auth': auth, + }); + + return IdServerUnbindResult.values.firstWhere( + (i) => + i.toString().split('.').last == response['id_server_unbind_result'], + ); + } + + Future usernameAvailable(String username) async { + final response = await request( + RequestType.GET, + '/client/r0/register/available?username=$username', + ); + return response['available']; + } + + /// Gets a list of the third party identifiers that the homeserver has + /// associated with the user's account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-register-available + Future> requestThirdPartyIdentifiers() async { + final response = await request( + RequestType.GET, + '/client/r0/account/3pid', + ); + return (response['threepids'] as List) + .map((item) => ThirdPartyIdentifier.fromJson(item)) + .toList(); + } + + /// Adds contact information to the user's account. Homeservers + /// should use 3PIDs added through this endpoint for password resets + /// instead of relying on the identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-add + Future addThirdPartyIdentifier( + String clientSecret, + String sid, { + Map auth, + }) async { + await request(RequestType.POST, '/client/r0/account/3pid/add', data: { + 'sid': sid, + 'client_secret': clientSecret, + if (auth != null && auth.isNotEmpty) 'auth': auth, + }); + return; + } + + /// Binds a 3PID to the user's account through the specified identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-bind + Future bindThirdPartyIdentifier( + String clientSecret, + String sid, + String idServer, + String idAccessToken, + ) async { + await request(RequestType.POST, '/client/r0/account/3pid/bind', data: { + 'sid': sid, + 'client_secret': clientSecret, + 'id_server': idServer, + 'id_access_token': idAccessToken, + }); + return; + } + + /// Removes a third party identifier from the user's account. This might not cause an unbind of the identifier from the identity server. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-delete + Future deleteThirdPartyIdentifier( + String address, + ThirdPartyIdentifierMedium medium, + String idServer, + ) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/delete', + data: { + 'address': address, + 'medium': medium.toString().split('.').last, + 'id_server': idServer, + }); + return IdServerUnbindResult.values.firstWhere( + (i) => + i.toString().split('.').last == response['id_server_unbind_result'], + ); + } + + /// Removes a user's third party identifier from the provided identity server without removing it from the homeserver. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-unbind + Future unbindThirdPartyIdentifier( + String address, + ThirdPartyIdentifierMedium medium, + String idServer, + ) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/unbind', + data: { + 'address': address, + 'medium': medium.toString().split('.').last, + 'id_server': idServer, + }); + return IdServerUnbindResult.values.firstWhere( + (i) => + i.toString().split('.').last == response['id_server_unbind_result'], + ); + } + + /// This API should be used to request validation tokens when adding an email address to an account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-email-requesttoken + Future requestEmailValidationToken( + String email, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/email/requestToken', + data: { + 'email': email, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// This API should be used to request validation tokens when adding a phone number to an account. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-account-3pid-msisdn-requesttoken + Future requestMsisdnValidationToken( + String country, + String phoneNumber, + String clientSecret, + int sendAttempt, { + String nextLink, + String idServer, + String idAccessToken, + }) async { + final response = await request( + RequestType.POST, '/client/r0/account/3pid/msisdn/requestToken', + data: { + 'country': country, + 'phone_number': phoneNumber, + 'send_attempt': sendAttempt, + 'client_secret': clientSecret, + if (nextLink != null) 'next_link': nextLink, + if (idServer != null) 'id_server': idServer, + if (idAccessToken != null) 'id_access_token': idAccessToken, + }); + return RequestTokenResponse.fromJson(response); + } + + /// Gets information about the owner of a given access token. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-account-whoami + Future whoAmI() async { + final response = await request( + RequestType.GET, + '/client/r0/account/whoami', + ); + return response['user_id']; + } + + /// Gets information about the server's supported feature set and other relevant capabilities. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-capabilities + Future requestServerCapabilities() async { + final response = await request( + RequestType.GET, + '/client/r0/capabilities', + ); + return ServerCapabilities.fromJson(response['capabilities']); + } + + /// Uploads a new filter definition to the homeserver. Returns a filter ID that may be used + /// in future requests to restrict which events are returned to the client. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-user-userid-filter + Future uploadFilter( + String userId, + Filter filter, + ) async { + final response = await request( + RequestType.POST, + '/client/r0/user/${Uri.encodeComponent(userId)}/filter', + data: filter.toJson(), + ); + return response['filter_id']; + } + + /// Download a filter + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-user-userid-filter + Future downloadFilter(String userId, String filterId) async { + final response = await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeComponent(userId)}/filter/${Uri.encodeComponent(filterId)}', + ); + return Filter.fromJson(response); + } + + /// Synchronise the client's state with the latest state on the server. Clients use this API when + /// they first log in to get an initial snapshot of the state on the server, and then continue to + /// call this API to get incremental deltas to the state, and to receive new messages. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-sync + Future sync({ + String filter, + String since, + bool fullState, + PresenceType setPresence, + int timeout, + }) async { + var atLeastOneParameter = false; + var action = '/client/r0/sync'; + if (filter != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'filter=${Uri.encodeQueryComponent(filter)}'; + atLeastOneParameter = true; + } + if (since != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'since=${Uri.encodeQueryComponent(since)}'; + atLeastOneParameter = true; + } + if (fullState != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'full_state=${Uri.encodeQueryComponent(fullState.toString())}'; + atLeastOneParameter = true; + } + if (setPresence != null) { + action += atLeastOneParameter ? '&' : '?'; + action += + 'set_presence=${Uri.encodeQueryComponent(setPresence.toString().split('.').last)}'; + atLeastOneParameter = true; + } + if (timeout != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'timeout=${Uri.encodeQueryComponent(timeout.toString())}'; + atLeastOneParameter = true; + } + final response = await request( + RequestType.GET, + action, + ); + return SyncUpdate.fromJson(response); + } + + /// Get a single event based on roomId/eventId. You must have permission to + /// retrieve this event e.g. by being a member in the room for this event. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-event-eventid + Future requestEvent(String roomId, String eventId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/event/${Uri.encodeComponent(eventId)}', + ); + return MatrixEvent.fromJson(response); + } + + /// Looks up the contents of a state event in a room. If the user is joined to the room then the + /// state is taken from the current state of the room. If the user has left the room then the + /// state is taken from the state of the room when they left. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey + Future> requestStateContent( + String roomId, String eventType, + [String stateKey]) async { + var url = + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/'; + if (stateKey != null) { + url += Uri.encodeComponent(stateKey); + } + final response = await request( + RequestType.GET, + url, + ); + return response; + } + + /// Get the state events for the current state of a room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-state + Future> requestStates(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/state', + ); + return (response['chunk'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList(); + } + + /// Get the list of members for this room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members + Future> requestMembers( + String roomId, { + String at, + Membership membership, + Membership notMembership, + }) async { + var action = '/client/r0/rooms/${Uri.encodeComponent(roomId)}/members'; + var atLeastOneParameter = false; + if (at != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'at=${Uri.encodeQueryComponent(at)}'; + atLeastOneParameter = true; + } + if (membership != null) { + action += atLeastOneParameter ? '&' : '?'; + action += + 'membership=${Uri.encodeQueryComponent(membership.toString().split('.').last)}'; + atLeastOneParameter = true; + } + if (notMembership != null) { + action += atLeastOneParameter ? '&' : '?'; + action += + 'not_membership=${Uri.encodeQueryComponent(notMembership.toString().split('.').last)}'; + atLeastOneParameter = true; + } + final response = await request( + RequestType.GET, + action, + ); + return (response['chunk'] as List) + .map((i) => MatrixEvent.fromJson(i)) + .toList(); + } + + /// This API returns a map of MXIDs to member info objects for members of the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-joined-members + Future> requestJoinedMembers(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/joined_members', + ); + return (response['joined'] as Map).map( + (k, v) => MapEntry(k, Profile.fromJson(v)), + ); + } + + /// This API returns a list of message and state events for a room. It uses pagination query + /// parameters to paginate history in the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-messages + Future requestMessages( + String roomId, + String from, + Direction dir, { + String to, + int limit, + String filter, + }) async { + var action = '/client/r0/rooms/${Uri.encodeComponent(roomId)}/messages'; + action += '?from=${Uri.encodeQueryComponent(from)}'; + action += + '&dir=${Uri.encodeQueryComponent(dir.toString().split('.').last)}'; + if (to != null) { + action += '&to=${Uri.encodeQueryComponent(to)}'; + } + if (limit != null) { + action += '&limit=${Uri.encodeQueryComponent(limit.toString())}'; + } + if (filter != null) { + action += '&filter=${Uri.encodeQueryComponent(filter)}'; + } + final response = await request( + RequestType.GET, + action, + ); + return TimelineHistoryResponse.fromJson(response); + } + + /// State events can be sent using this endpoint. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey + Future sendState( + String roomId, + String eventType, + Map content, [ + String stateKey = '', + ]) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/state/${Uri.encodeQueryComponent(eventType)}/${Uri.encodeQueryComponent(stateKey)}', + data: content); + return response['event_id']; + } + + /// This endpoint is used to send a message event to a room. + /// Message events allow access to historical events and pagination, + /// making them suited for "once-off" activity in a room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid + Future sendMessage( + String roomId, + String eventType, + String txnId, + Map content, + ) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/send/${Uri.encodeQueryComponent(eventType)}/${Uri.encodeQueryComponent(txnId)}', + data: content); + return response['event_id']; + } + + /// Strips all information out of an event which isn't critical to the integrity of + /// the server-side representation of the room. + /// https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid + Future redact( + String roomId, + String eventId, + String txnId, { + String reason, + }) async { + final response = await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/redact/${Uri.encodeQueryComponent(eventId)}/${Uri.encodeQueryComponent(txnId)}', + data: { + if (reason != null) 'reason': reason, + }); + return response['event_id']; + } + + Future createRoom({ + Visibility visibility, + String roomAliasName, + String name, + String topic, + List invite, + List> invite3pid, + String roomVersion, + Map creationContent, + List> initialState, + CreateRoomPreset preset, + bool isDirect, + Map powerLevelContentOverride, + }) async { + final response = + await request(RequestType.POST, '/client/r0/createRoom', data: { + if (visibility != null) + 'visibility': visibility.toString().split('.').last, + if (roomAliasName != null) 'room_alias_name': roomAliasName, + if (name != null) 'name': name, + if (topic != null) 'topic': topic, + if (invite != null) 'invite': invite, + if (invite3pid != null) 'invite_3pid': invite3pid, + if (roomVersion != null) 'room_version': roomVersion, + if (creationContent != null) 'creation_content': creationContent, + if (initialState != null) 'initial_state': initialState, + if (preset != null) 'preset': preset.toString().split('.').last, + if (isDirect != null) 'is_direct': isDirect, + if (powerLevelContentOverride != null) + 'power_level_content_override': powerLevelContentOverride, + }); + return response['room_id']; + } + + /// Create a new mapping from room alias to room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-directory-room-roomalias + Future createRoomAlias(String alias, String roomId) async { + await request( + RequestType.PUT, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + data: {'room_id': roomId}, + ); + return; + } + + /// Requests that the server resolve a room alias to a room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-directory-room-roomalias + Future requestRoomAliasInformations( + String alias) async { + final response = await request( + RequestType.GET, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + ); + return RoomAliasInformations.fromJson(response); + } + + /// Remove a mapping of room alias to room ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-directory-room-roomalias + Future removeRoomAlias(String alias) async { + await request( + RequestType.DELETE, + '/client/r0/directory/room/${Uri.encodeComponent(alias)}', + ); + return; + } + + /// Get a list of aliases maintained by the local server for the given room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases + Future> requestRoomAliases(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/room/${Uri.encodeComponent(roomId)}/aliases', + ); + return List.from(response['aliases']); + } + + /// This API returns a list of the user's current rooms. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-joined-rooms + Future> requestJoinedRooms() async { + final response = await request( + RequestType.GET, + '/client/r0/joined_rooms', + ); + return List.from(response['joined_rooms']); + } + + /// This API invites a user to participate in a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-invite + Future inviteToRoom(String roomId, String userId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/invite', + data: { + 'user_id': userId, + }, + ); + return; + } + + /// This API starts a user participating in a particular room, if that user is allowed to participate in that room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-invite + Future joinRoom( + String roomId, { + String thirdPidSignedSender, + String thirdPidSignedmxid, + String thirdPidSignedToken, + Map thirdPidSignedSiganture, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/join', + data: { + if (thirdPidSignedSiganture != null) + 'third_party_signed': { + 'sender': thirdPidSignedSender, + 'mxid': thirdPidSignedmxid, + 'token': thirdPidSignedToken, + 'signatures': thirdPidSignedSiganture, + } + }, + ); + return response['room_id']; + } + + /// This API starts a user participating in a particular room, if that user is allowed to participate in that room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias + Future joinRoomOrAlias( + String roomIdOrAlias, { + List servers, + String thirdPidSignedSender, + String thirdPidSignedmxid, + String thirdPidSignedToken, + Map thirdPidSignedSiganture, + }) async { + var action = '/client/r0/join/${Uri.encodeComponent(roomIdOrAlias)}'; + if (servers != null) { + for (var i = 0; i < servers.length; i++) { + if (i == 0) { + action += '?'; + } else { + action += '&'; + } + action += 'server_name=${Uri.encodeQueryComponent(servers[i])}'; + } + } + final response = await request( + RequestType.POST, + action, + data: { + if (thirdPidSignedSiganture != null) + 'third_party_signed': { + 'sender': thirdPidSignedSender, + 'mxid': thirdPidSignedmxid, + 'token': thirdPidSignedToken, + 'signatures': thirdPidSignedSiganture, + } + }, + ); + return response['room_id']; + } + + /// This API stops a user participating in a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-leave + Future leaveRoom(String roomId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/leave', + ); + return; + } + + /// This API stops a user remembering about a particular room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-forget + Future forgetRoom(String roomId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/forget', + ); + return; + } + + /// Kick a user from the room. + /// The caller must have the required power level in order to perform this operation. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-kick + Future kickFromRoom(String roomId, String userId, + {String reason}) async { + await request(RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/kick', + data: { + 'user_id': userId, + if (reason != null) 'reason': reason, + }); + return; + } + + /// Ban a user in the room. If the user is currently in the room, also kick them. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-ban + Future banFromRoom(String roomId, String userId, + {String reason}) async { + await request( + RequestType.POST, '/client/r0/rooms/${Uri.encodeComponent(roomId)}/ban', + data: { + 'user_id': userId, + if (reason != null) 'reason': reason, + }); + return; + } + + /// Unban a user from the room. This allows them to be invited to the room, and join if they + /// would otherwise be allowed to join according to its join rules. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-unban + Future unbanInRoom(String roomId, String userId) async { + await request( + RequestType.POST, '/client/r0/rooms/${Uri.encodeComponent(roomId)}/ban', + data: { + 'user_id': userId, + }); + return; + } + + /// Gets the visibility of a given room on the server's public room directory. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-directory-list-room-roomid + Future requestRoomVisibility(String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/directory/list/room/${Uri.encodeComponent(roomId)}', + ); + return Visibility.values.firstWhere( + (v) => v.toString().split('.').last == response['visibility']); + } + + /// Sets the visibility of a given room in the server's public room directory. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-directory-list-room-roomid + Future setRoomVisibility(String roomId, Visibility visibility) async { + await request( + RequestType.PUT, + '/client/r0/directory/list/room/${Uri.encodeComponent(roomId)}', + data: { + 'visibility': visibility.toString().split('.').last, + }, + ); + return; + } + + /// Lists the public rooms on the server. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-publicrooms + Future requestPublicRooms({ + int limit, + String since, + String server, + }) async { + var action = '/client/r0/publicRooms'; + var atLeastOneParameter = false; + if (limit != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'limit=${Uri.encodeQueryComponent(limit.toString())}'; + atLeastOneParameter = true; + } + if (since != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'since=${Uri.encodeQueryComponent(since.toString())}'; + atLeastOneParameter = true; + } + if (server != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'server=${Uri.encodeQueryComponent(server.toString())}'; + atLeastOneParameter = true; + } + final response = await request( + RequestType.GET, + action, + ); + return PublicRoomsResponse.fromJson(response); + } + + /// Lists the public rooms on the server, with optional filter. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-publicrooms + Future searchPublicRooms({ + String genericSearchTerm, + int limit, + String since, + String server, + bool includeAllNetworks, + String thirdPartyInstanceId, + }) async { + var action = '/client/r0/publicRooms'; + if (server != null) { + action += '?server=${Uri.encodeQueryComponent(server.toString())}'; + } + final response = await request( + RequestType.POST, + action, + data: { + if (limit != null) 'limit': limit, + if (since != null) 'since': since, + if (includeAllNetworks != null) + 'include_all_networks': includeAllNetworks, + if (thirdPartyInstanceId != null) + 'third_party_instance_id': thirdPartyInstanceId, + if (genericSearchTerm != null) + 'filter': { + 'generic_search_term': genericSearchTerm, + }, + }, + ); + return PublicRoomsResponse.fromJson(response); + } + + /// Performs a search for users. The homeserver may determine which subset of users are searched, + /// however the homeserver MUST at a minimum consider the users the requesting user shares a + /// room with and those who reside in public rooms (known to the homeserver). The search MUST + /// consider local users to the homeserver, and SHOULD query remote users as part of the search. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-user-directory-search + Future searchUser( + String searchTerm, { + int limit, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/user_directory/search', + data: { + 'search_term': searchTerm, + if (limit != null) 'limit': limit, + }, + ); + return UserSearchResult.fromJson(response); + } + + /// This API sets the given user's display name. You must have permission to + /// set this user's display name, e.g. you need to have their access_token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-profile-userid-displayname + Future setDisplayname(String userId, String displayname) async { + await request( + RequestType.PUT, + '/client/r0/profile/${Uri.encodeComponent(userId)}/displayname', + data: { + 'displayname': displayname, + }, + ); + return; + } + + /// Get the user's display name. This API may be used to fetch the user's own + /// displayname or to query the name of other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-displayname + Future requestDisplayname(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}/displayname', + ); + return response['displayname']; + } + + /// This API sets the given user's avatar URL. You must have permission to set + /// this user's avatar URL, e.g. you need to have their access_token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-profile-userid-avatar-url + Future setAvatarUrl(String userId, Uri avatarUrl) async { + await request( + RequestType.PUT, + '/client/r0/profile/${Uri.encodeComponent(userId)}/avatar_url', + data: { + 'avatar_url': avatarUrl.toString(), + }, + ); + return; + } + + /// Get the user's avatar URL. This API may be used to fetch the user's own avatar URL or to + /// query the URL of other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-avatar-url + Future requestAvatarUrl(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}/avatar_url', + ); + return Uri.parse(response['avatar_url']); + } + + /// Get the combined profile information for this user. This API may be used to fetch the user's + /// own profile information or other users; either locally or on remote homeservers. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-profile-userid-avatar-url + Future requestProfile(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/profile/${Uri.encodeComponent(userId)}', + ); + return Profile.fromJson(response); + } + + /// This API provides credentials for the client to use when initiating calls. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-voip-turnserver + Future requestTurnServerCredentials() async { + final response = await request( + RequestType.GET, + '/client/r0/voip/turnServer', + ); + return TurnServerCredentials.fromJson(response); + } + + /// This tells the server that the user is typing for the next N milliseconds + /// where N is the value specified in the timeout key. Alternatively, if typing is false, + /// it tells the server that the user has stopped typing. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-typing-userid + Future sendTypingNotification( + String userId, + String roomId, + bool typing, { + int timeout, + }) async { + await request(RequestType.PUT, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/typing/${Uri.encodeComponent(userId)}', + data: { + 'typing': typing, + if (timeout != null) 'timeout': timeout, + }); + return; + } + + /// This API updates the marker for the given receipt type to the event ID specified. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-receipt-receipttype-eventid + /// + Future sendReceiptMarker(String roomId, String eventId) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/receipt/m.read/${Uri.encodeComponent(eventId)}', + ); + return; + } + + /// Sets the position of the read marker for a given room, and optionally the read receipt's location. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-read-markers + Future sendReadMarker(String roomId, String eventId, + {String readReceiptLocationEventId}) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/read_markers', + data: { + 'm.fully_read': eventId, + if (readReceiptLocationEventId != null) + 'm.read': readReceiptLocationEventId, + }, + ); + return; + } + + /// This API sets the given user's presence state. When setting the status, + /// the activity time is updated to reflect that activity; the client does not need + /// to specify the last_active_ago field. You cannot set the presence state of another user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-presence-userid-status + Future sendPresence( + String userId, + PresenceType presenceType, { + String statusMsg, + }) async { + await request( + RequestType.POST, + '/client/r0/presence/${Uri.encodeComponent(userId)}/status', + data: { + 'presence': presenceType.toString().split('.').last, + if (statusMsg != null) 'status_msg': statusMsg, + }, + ); + return; + } + + /// Get the given user's presence state. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-presence-userid-status + Future requestPresence(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/presence/${Uri.encodeComponent(userId)}/status', + ); + return PresenceContent.fromJson(response); + } + + /// Uploads a file with the name [fileName] as base64 encoded to the server + /// and returns the mxc url as a string. + /// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-media-r0-upload + Future upload(Uint8List file, String fileName, + {String contentType}) async { + fileName = fileName.split('/').last; + var headers = {}; + headers['Authorization'] = 'Bearer $accessToken'; + headers['Content-Type'] = contentType ?? mime(fileName); + fileName = Uri.encodeQueryComponent(fileName); + final url = + '$homeserver.toString()/_matrix/media/r0/upload?filename=$fileName'; + final streamedRequest = http.StreamedRequest('POST', Uri.parse(url)) + ..headers.addAll(headers); + streamedRequest.contentLength = await file.length; + streamedRequest.sink.add(file); + streamedRequest.sink.close(); + if (debug) print('[UPLOADING] $fileName'); + var streamedResponse = _testMode ? null : await streamedRequest.send(); + Map jsonResponse = json.decode( + String.fromCharCodes(_testMode + ? ((fileName == 'file.jpeg') + ? '{"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"}' + : '{"errcode":"M_FORBIDDEN","error":"Cannot upload this content"}') + .codeUnits + : await streamedResponse.stream.first), + ); + if (!jsonResponse.containsKey('content_uri')) { + throw MatrixException.fromJson(jsonResponse); + } + return jsonResponse['content_uri']; + } + + /// Get information about a URL for the client. Typically this is called when a client sees a + /// URL in a message and wants to render a preview for the user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-preview-url + Future requestOpenGraphDataForUrl(Uri url, {int ts}) async { + var action = + '${homeserver.toString()}/_matrix/media/r0/preview_url?url=${Uri.encodeQueryComponent(url.toString())}'; + if (ts != null) { + action += '&ts=${Uri.encodeQueryComponent(ts.toString())}'; + } + final response = await httpClient.get(action); + final rawJson = json.decode(response.body.isEmpty ? '{}' : response.body); + return OpenGraphData.fromJson(rawJson); + } + + /// This endpoint allows clients to retrieve the configuration of the content repository, such as upload limitations. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config + Future requestMaxUploadSize() async { + var action = '${homeserver.toString()}/_matrix/media/r0/config'; + final response = await httpClient.get(action); + final rawJson = json.decode(response.body.isEmpty ? '{}' : response.body); + return rawJson['m.upload.size']; + } + + /// 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 { + await request( + RequestType.PUT, + '/client/r0/sendToDevice/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}', + data: { + 'messages': messages, + }, + ); + return; + } + + /// Gets information about all devices for the current user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices + Future> requestDevices() async { + final response = await request( + RequestType.GET, + '/client/r0/devices', + ); + return (response['devices'] as List) + .map((i) => Device.fromJson(i)) + .toList(); + } + + /// Gets information on a single device, by device id. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices-deviceid + Future requestDevice(String deviceId) async { + final response = await request( + RequestType.GET, + '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + ); + return Device.fromJson(response); + } + + /// Updates the metadata on the given device. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-devices-deviceid + Future setDeviceMetadata(String deviceId, {String displayName}) async { + await request( + RequestType.PUT, '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + data: { + if (displayName != null) 'display_name': displayName, + }); + return; + } + + /// Deletes the given device, and invalidates any access token associated with it. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-devices-deviceid + Future deleteDevice(String deviceId, + {Map auth}) async { + await request(RequestType.DELETE, + '/client/r0/devices/${Uri.encodeComponent(deviceId)}', + data: { + if (auth != null) 'auth': auth, + }); + return; + } + + /// Deletes the given devices, and invalidates any access token associated with them. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-delete-devices + Future deleteDevices(List deviceIds, + {Map auth}) async { + await request(RequestType.POST, '/client/r0/delete_devices', data: { + 'devices': deviceIds, + if (auth != null) 'auth': auth, + }); + return; + } + + /// Publishes end-to-end encryption keys for the device. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query + Future> uploadDeviceKeys( + {MatrixDeviceKeys deviceKeys, Map oneTimeKeys}) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/upload', + data: { + if (deviceKeys != null) 'device_keys': deviceKeys.toJson(), + if (oneTimeKeys != null) 'one_time_keys': oneTimeKeys, + }, + ); + return Map.from(response['one_time_key_counts']); + } + + /// Returns the current devices and identity keys for the given users. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-query + Future requestDeviceKeys( + Map deviceKeys, { + int timeout, + String token, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/query', + data: { + 'device_keys': deviceKeys, + if (timeout != null) 'timeout': timeout, + if (token != null) 'token': token, + }, + ); + return KeysQueryResponse.fromJson(response); + } + + /// Claims one-time keys for use in pre-key messages. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-claim + Future requestOneTimeKeys( + Map> oneTimeKeys, { + int timeout, + }) async { + final response = await request( + RequestType.POST, + '/client/r0/keys/claim', + data: { + 'one_time_keys': oneTimeKeys, + if (timeout != null) 'timeout': timeout, + }, + ); + return OneTimeKeysClaimResponse.fromJson(response); + } + + /// Gets a list of users who have updated their device identity keys since a previous sync token. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-keys-upload + Future requestDeviceListsUpdate( + String from, String to) async { + final response = await request( + RequestType.GET, + '/client/r0/keys/changes?from=${Uri.encodeQueryComponent(from)}&to=${Uri.encodeQueryComponent(to)}', + ); + return DeviceListsUpdate.fromJson(response); + } + + /// Gets all currently active pushers for the authenticated user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushers + Future> requestPushers() async { + final response = await request( + RequestType.GET, + '/client/r0/pushers', + ); + return (response['pushers'] as List) + .map((i) => Pusher.fromJson(i)) + .toList(); + } + + /// This endpoint allows the creation, modification and deletion of pushers + /// for this user ID. The behaviour of this endpoint varies depending on the + /// values in the JSON body. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-pushers-set + Future setPusher(Pusher pusher, {bool append}) async { + var data = pusher.toJson(); + if (append != null) { + data['append'] = append; + } + await request( + RequestType.POST, + '/client/r0/pushers/set', + ); + return; + } + + /// This API is used to paginate through the list of events that the user has + /// been, or would have been notified about. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-notifications + Future requestNotifications({ + String from, + int limit, + String only, + }) async { + var action = '/client/r0/notifications'; + var atLeastOneParameter = false; + if (from != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'from=${Uri.encodeQueryComponent(from.toString())}'; + atLeastOneParameter = true; + } + if (limit != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'limit=${Uri.encodeQueryComponent(limit.toString())}'; + atLeastOneParameter = true; + } + if (only != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'only=${Uri.encodeQueryComponent(only.toString())}'; + atLeastOneParameter = true; + } + final response = await request( + RequestType.GET, + action, + ); + return NotificationsQueryResponse.fromJson(response); + } + + /// Retrieve all push rulesets for this user. Clients can "drill-down" + /// on the rulesets by suffixing a scope to this path e.g. /pushrules/global/. + /// This will return a subset of this data under the specified key e.g. the global key. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules + Future requestPushRules() async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules', + ); + return PushRuleSet.fromJson(response['global']); + } + + /// Retrieve a single specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid + Future requestPushRule( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}', + ); + return PushRule.fromJson(response); + } + + /// This endpoint removes the push rule defined in the path. + /// https://matrix.org/docs/spec/client_server/r0.6.1#delete-matrix-client-r0-pushrules-scope-kind-ruleid + Future deletePushRule( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + await request( + RequestType.DELETE, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}', + ); + return; + } + + /// This endpoint allows the creation, modification and deletion of pushers for this user ID. + /// The behaviour of this endpoint varies depending on the values in the JSON body. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid + Future setPushRule( + String scope, + PushRuleKind kind, + String ruleId, + List actions, { + String before, + String after, + List conditions, + String pattern, + }) async { + var action = + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}'; + var atLeastOneParameter = false; + if (before != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'before=${Uri.encodeQueryComponent(before.toString())}'; + atLeastOneParameter = true; + } + if (after != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'after=${Uri.encodeQueryComponent(after.toString())}'; + atLeastOneParameter = true; + } + await request(RequestType.PUT, action, data: { + 'actions': actions.map((i) => i.toString().split('.').last).toList(), + if (conditions != null) + 'conditions': + conditions.map((i) => i.toString().split('.').last).toList(), + if (pattern != null) 'pattern': pattern, + }); + return; + } + + /// This endpoint gets whether the specified push rule is enabled. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid-enabled + Future requestPushRuleEnabled( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}/enabled', + ); + return response['enabled']; + } + + /// This endpoint allows clients to enable or disable the specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid-enabled + Future enablePushRule( + String scope, + PushRuleKind kind, + String ruleId, + bool enabled, + ) async { + await request( + RequestType.PUT, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}/enabled', + data: {'enabled': enabled}, + ); + return; + } + + /// This endpoint get the actions for the specified push rule. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-pushrules-scope-kind-ruleid-actions + Future> requestPushRuleActions( + String scope, + PushRuleKind kind, + String ruleId, + ) async { + final response = await request( + RequestType.GET, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}/actions', + ); + return (response['actions'] as List) + .map((i) => PushRuleAction.values + .firstWhere((a) => a.toString().split('.').last == i)) + .toList(); + } + + /// This endpoint allows clients to change the actions of a push rule. This can be used to change the actions of builtin rules. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions + Future setPushRuleActions( + String scope, + PushRuleKind kind, + String ruleId, + List actions, + ) async { + await request( + RequestType.PUT, + '/client/r0/pushrules/${Uri.encodeComponent(scope)}/${Uri.encodeComponent(kind.toString().split('.').last)}/${Uri.encodeComponent(ruleId)}/actions', + data: { + 'actions': actions.map((a) => a.toString().split('.').last).toList() + }, + ); + return; + } + + /// Performs a full text search across different categories. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search + /// Please note: The specification is not 100% clear what it is expecting and sending here. + /// So we stick with pure json until we have more informations. + Future> globalSearch(Map query) async { + return await request( + RequestType.POST, + '/client/r0/search', + data: query, + ); + } + + /// This will listen for new events related to a particular room and return them to the + /// caller. This will block until an event is received, or until the timeout is reached. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-events + Future requestEvents({ + String from, + int timeout, + String roomId, + }) async { + var action = '/client/r0/events'; + var atLeastOneParameter = false; + if (from != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'from=${Uri.encodeQueryComponent(from)}'; + atLeastOneParameter = true; + } + if (timeout != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'timeout=${Uri.encodeQueryComponent(timeout.toString())}'; + atLeastOneParameter = true; + } + if (roomId != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'roomId=${Uri.encodeQueryComponent(roomId)}'; + atLeastOneParameter = true; + } + final response = await request(RequestType.GET, action); + return EventsSyncUpdate.fromJson(response); + } + + /// List the tags set by a user on a room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-rooms-roomid-tags + Future> requestRoomTags(String userId, String roomId) async { + final response = await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags', + ); + return (response['tags'] as Map).map( + (k, v) => MapEntry(k, Tag.fromJson(v)), + ); + } + + /// Add a tag to the room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-tags-tag + Future addRoomTag( + String userId, + String roomId, + String tag, { + double order, + }) async { + await request(RequestType.PUT, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags/${Uri.encodeQueryComponent(tag)}', + data: { + if (order != null) 'order': order, + }); + return; + } + + /// Remove a tag from the room. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-tags-tag + Future removeRoomTag(String userId, String roomId, String tag) async { + await request( + RequestType.DELETE, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags/${Uri.encodeQueryComponent(tag)}', + ); + return; + } + + /// Set some account_data for the client. This config is only visible to the user that set the account_data. + /// The config will be synced to clients in the top-level account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-account-data-type + Future setAccountData( + String userId, + String type, + Map content, + ) async { + await request( + RequestType.PUT, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/account_data/${Uri.encodeQueryComponent(type)}', + data: content, + ); + return; + } + + /// Get some account_data for the client. This config is only visible to the user that set the account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-account-data-type + Future> requestAccountData( + String userId, + String type, + ) async { + return await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/account_data/${Uri.encodeQueryComponent(type)}', + ); + } + + /// Set some account_data for the client on a given room. This config is only visible to the user that set + /// the account_data. The config will be synced to clients in the per-room account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-user-userid-rooms-roomid-account-data-type + Future setRoomAccountData( + String userId, + String roomId, + String type, + Map content, + ) async { + await request( + RequestType.PUT, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/account_data/${Uri.encodeQueryComponent(type)}', + data: content, + ); + return; + } + + /// Get some account_data for the client on a given room. This config is only visible to the user that set the account_data. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-user-userid-rooms-roomid-account-data-type + Future> requestRoomAccountData( + String userId, + String roomId, + String type, + ) async { + return await request( + RequestType.GET, + '/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/account_data/${Uri.encodeQueryComponent(type)}', + ); + } + + /// Gets information about a particular user. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-admin-whois-userid + Future requestWhoIsInfo(String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/admin/whois/${Uri.encodeQueryComponent(userId)}', + ); + return WhoIsInfo.fromJson(response); + } + + /// This API returns a number of events that happened just before and after the specified event. + /// This allows clients to get the context surrounding an event. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-context-eventid + Future requestEventContext( + String roomId, + String eventId, { + int limit, + String filter, + }) async { + var action = + '/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/context/${Uri.encodeQueryComponent(eventId)}'; + var atLeastOneParameter = false; + if (filter != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'filter=${Uri.encodeQueryComponent(filter)}'; + atLeastOneParameter = true; + } + if (limit != null) { + action += atLeastOneParameter ? '&' : '?'; + action += 'limit=${Uri.encodeQueryComponent(limit.toString())}'; + atLeastOneParameter = true; + } + final response = await request(RequestType.GET, action); + return EventContext.fromJson(response); + } + + /// Reports an event as inappropriate to the server, which may then notify the appropriate people. + /// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-rooms-roomid-report-eventid + Future reportEvent( + String roomId, + String eventId, + String reason, + int score, + ) async { + await request(RequestType.POST, + '/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/report/${Uri.encodeQueryComponent(eventId)}', + data: { + 'reason': reason, + 'score': score, + }); + return; + } + + /// Fetches the overall metadata about protocols supported by the homeserver. Includes + /// both the available protocols and all fields required for queries against each protocol. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-protocols + Future> requestSupportedProtocols() async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/protocols', + ); + return response.map((k, v) => MapEntry(k, SupportedProtocol.fromJson(v))); + } + + /// Fetches the metadata from the homeserver about a particular third party protocol. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-protocol-protocol + Future requestSupportedProtocol(String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/protocol/${Uri.encodeComponent(protocol)}', + ); + return SupportedProtocol.fromJson(response); + } + + /// Requesting this endpoint with a valid protocol name results in a list of successful + /// mapping results in a JSON array. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-location-protocol + Future> requestThirdPartyLocations( + String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/location/${Uri.encodeComponent(protocol)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyLocation.fromJson(i)) + .toList(); + } + + /// Retrieve a Matrix User ID linked to a user on the third party service, given a set of + /// user parameters. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol + Future> requestThirdPartyUsers(String protocol) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/user/${Uri.encodeComponent(protocol)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyUser.fromJson(i)) + .toList(); + } + + /// Retrieve an array of third party network locations from a Matrix room alias. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-location + Future> requestThirdPartyLocationsByAlias( + String alias) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/location?alias=${Uri.encodeComponent(alias)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyLocation.fromJson(i)) + .toList(); + } + + /// Retrieve an array of third party users from a Matrix User ID. + /// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user + Future> requestThirdPartyUsersByUserId( + String userId) async { + final response = await request( + RequestType.GET, + '/client/r0/thirdparty/user?userid=${Uri.encodeComponent(userId)}', + ); + return (response['chunk'] as List) + .map((i) => ThirdPartyUser.fromJson(i)) + .toList(); + } + + Future requestOpenIdCredentials(String userId) async { + final response = await request( + RequestType.POST, + '/client/r0/user/${Uri.encodeComponent(userId)}/openid/request_token', + ); + return OpenIdCredentials.fromJson(response); + } + + Future upgradeRoom(String roomId, String version) async { + await request( + RequestType.POST, + '/client/r0/rooms/${Uri.encodeComponent(roomId)}/upgrade', + data: {'new_version': version}, + ); + return; + } +} diff --git a/lib/matrix_api/model/basic_event.dart b/lib/matrix_api/model/basic_event.dart new file mode 100644 index 0000000..095b2b2 --- /dev/null +++ b/lib/matrix_api/model/basic_event.dart @@ -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 . + */ + +class BasicEvent { + String type; + Map content; + + BasicEvent({ + this.type, + this.content, + }); + + BasicEvent.fromJson(Map json) { + type = json['type']; + content = Map.from(json['content']); + } + Map toJson() { + final data = {}; + data['type'] = type; + data['content'] = content; + return data; + } +} diff --git a/lib/matrix_api/model/basic_event_with_sender.dart b/lib/matrix_api/model/basic_event_with_sender.dart new file mode 100644 index 0000000..0e50bd8 --- /dev/null +++ b/lib/matrix_api/model/basic_event_with_sender.dart @@ -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 . + */ + +import 'basic_event.dart'; + +class BasicEventWithSender extends BasicEvent { + String senderId; + + BasicEventWithSender(); + + BasicEventWithSender.fromJson(Map json) { + final basicEvent = BasicEvent.fromJson(json); + type = basicEvent.type; + content = basicEvent.content; + senderId = json['sender']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['sender'] = senderId; + return data; + } +} diff --git a/lib/matrix_api/model/basic_room_event.dart b/lib/matrix_api/model/basic_room_event.dart new file mode 100644 index 0000000..c8f7564 --- /dev/null +++ b/lib/matrix_api/model/basic_room_event.dart @@ -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 . + */ + +import 'package:famedlysdk/matrix_api/model/basic_event.dart'; + +class BasicRoomEvent extends BasicEvent { + String roomId; + + BasicRoomEvent({ + this.roomId, + Map content, + String type, + }) : super( + content: content, + type: type, + ); + + BasicRoomEvent.fromJson(Map json) { + final basicEvent = BasicEvent.fromJson(json); + content = basicEvent.content; + type = basicEvent.type; + roomId = json['room_id']; + } + + @override + Map toJson() { + final data = super.toJson(); + if (roomId != null) data['room_id'] = roomId; + return data; + } +} diff --git a/lib/matrix_api/model/device.dart b/lib/matrix_api/model/device.dart new file mode 100644 index 0000000..aa089a4 --- /dev/null +++ b/lib/matrix_api/model/device.dart @@ -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 . + */ + +class Device { + String deviceId; + String displayName; + String lastSeenIp; + DateTime lastSeenTs; + + Device.fromJson(Map json) { + deviceId = json['device_id']; + displayName = json['display_name']; + lastSeenIp = json['last_seen_ip']; + lastSeenTs = DateTime.fromMillisecondsSinceEpoch(json['last_seen_ts'] ?? 0); + } + + Map toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/event_context.dart b/lib/matrix_api/model/event_context.dart new file mode 100644 index 0000000..9d23961 --- /dev/null +++ b/lib/matrix_api/model/event_context.dart @@ -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 . + */ + +import 'matrix_event.dart'; + +class EventContext { + String end; + List eventsAfter; + MatrixEvent event; + List eventsBefore; + String start; + List state; + + EventContext.fromJson(Map json) { + end = json['end']; + if (json['events_after'] != null) { + eventsAfter = []; + 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 = []; + json['events_before'].forEach((v) { + eventsBefore.add(MatrixEvent.fromJson(v)); + }); + } + start = json['start']; + if (json['state'] != null) { + state = []; + json['state'].forEach((v) { + state.add(MatrixEvent.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/event_types.dart b/lib/matrix_api/model/event_types.dart new file mode 100644 index 0000000..4582b30 --- /dev/null +++ b/lib/matrix_api/model/event_types.dart @@ -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 . + */ + +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'; +} diff --git a/lib/matrix_api/model/events_sync_update.dart b/lib/matrix_api/model/events_sync_update.dart new file mode 100644 index 0000000..46b0fb3 --- /dev/null +++ b/lib/matrix_api/model/events_sync_update.dart @@ -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 . + */ + +import 'matrix_event.dart'; + +class EventsSyncUpdate { + String start; + String end; + List chunk; + + EventsSyncUpdate.fromJson(Map json) { + start = json['start']; + end = json['end']; + chunk = json['chunk'] != null + ? (json['chunk'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + } + + Map toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/filter.dart b/lib/matrix_api/model/filter.dart new file mode 100644 index 0000000..b1e18a7 --- /dev/null +++ b/lib/matrix_api/model/filter.dart @@ -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 . + */ + +enum EventFormat { client, federation } + +class Filter { + RoomFilter room; + EventFilter presence; + EventFilter accountData; + EventFormat eventFormat; + List eventFields; + + Filter({ + this.room, + this.presence, + this.accountData, + this.eventFormat, + this.eventFields, + }); + + Filter.fromJson(Map 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() + : null; + } + + Map toJson() { + final data = {}; + 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 notRooms; + List 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 json) { + notRooms = json['not_rooms']?.cast(); + rooms = json['rooms']?.cast(); + 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 toJson() { + final data = {}; + 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 senders; + List types; + List notRooms; + List notSenders; + + EventFilter( + {this.limit, this.senders, this.types, this.notRooms, this.notSenders}); + + EventFilter.fromJson(Map json) { + limit = json['limit']; + types = json['senders']?.cast(); + types = json['types']?.cast(); + notRooms = json['not_rooms']?.cast(); + notSenders = json['not_senders']?.cast(); + } + + Map toJson() { + final data = {}; + 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 notTypes; + bool lazyLoadMembers; + bool includeRedundantMembers; + bool containsUrl; + + StateFilter({ + this.notTypes, + this.lazyLoadMembers, + this.includeRedundantMembers, + this.containsUrl, + int limit, + List senders, + List types, + List notRooms, + List notSenders, + }) : super( + limit: limit, + senders: senders, + types: types, + notRooms: notRooms, + notSenders: notSenders, + ); + + StateFilter.fromJson(Map 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(); + lazyLoadMembers = json['lazy_load_members']; + includeRedundantMembers = json['include_redundant_members']; + containsUrl = json['contains_url']; + } + + @override + Map 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; + } +} diff --git a/lib/matrix_api/model/keys_query_response.dart b/lib/matrix_api/model/keys_query_response.dart new file mode 100644 index 0000000..8c6bb32 --- /dev/null +++ b/lib/matrix_api/model/keys_query_response.dart @@ -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 . + */ + +import 'matrix_device_keys.dart'; + +class KeysQueryResponse { + Map failures; + Map> deviceKeys; + + KeysQueryResponse.fromJson(Map json) { + failures = Map.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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/login_response.dart b/lib/matrix_api/model/login_response.dart new file mode 100644 index 0000000..727f6d8 --- /dev/null +++ b/lib/matrix_api/model/login_response.dart @@ -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 . + */ + +import 'well_known_informations.dart'; + +class LoginResponse { + String userId; + String accessToken; + String deviceId; + WellKnownInformations wellKnownInformations; + + LoginResponse.fromJson(Map 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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/login_types.dart b/lib/matrix_api/model/login_types.dart new file mode 100644 index 0000000..5cb67d2 --- /dev/null +++ b/lib/matrix_api/model/login_types.dart @@ -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 . + */ + +class LoginTypes { + List flows; + + LoginTypes.fromJson(Map json) { + if (json['flows'] != null) { + flows = []; + json['flows'].forEach((v) { + flows.add(Flows.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (flows != null) { + data['flows'] = flows.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Flows { + String type; + + Flows.fromJson(Map json) { + type = json['type']; + } + + Map toJson() { + final data = {}; + data['type'] = type; + return data; + } +} diff --git a/lib/matrix_api/model/matrix_device_keys.dart b/lib/matrix_api/model/matrix_device_keys.dart new file mode 100644 index 0000000..95a225b --- /dev/null +++ b/lib/matrix_api/model/matrix_device_keys.dart @@ -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 . + */ + +class MatrixDeviceKeys { + String userId; + String deviceId; + List algorithms; + Map keys; + Map> signatures; + Map 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 _json; + + MatrixDeviceKeys( + this.userId, + this.deviceId, + this.algorithms, + this.keys, + this.signatures, { + this.unsigned, + }); + + MatrixDeviceKeys.fromJson(Map json) { + _json = json; + userId = json['user_id']; + deviceId = json['device_id']; + algorithms = json['algorithms'].cast(); + keys = Map.from(json['keys']); + signatures = Map>.from( + (json['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))); + unsigned = json['unsigned'] != null + ? Map.from(json['unsigned']) + : null; + } + + Map toJson() { + final data = _json ?? {}; + 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; + } +} diff --git a/lib/matrix_api/model/matrix_event.dart b/lib/matrix_api/model/matrix_event.dart new file mode 100644 index 0000000..3d11471 --- /dev/null +++ b/lib/matrix_api/model/matrix_event.dart @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +import 'package:famedlysdk/matrix_api/model/stripped_state_event.dart'; + +class MatrixEvent extends StrippedStateEvent { + String eventId; + String roomId; + DateTime originServerTs; + Map unsigned; + Map prevContent; + + MatrixEvent(); + + MatrixEvent.fromJson(Map 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.from(json['unsigned']) + : null; + prevContent = json['prev_content'] != null + ? Map.from(json['prev_content']) + : null; + } + + @override + Map 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'); + } + return data; + } +} diff --git a/lib/src/utils/matrix_exception.dart b/lib/matrix_api/model/matrix_exception.dart similarity index 76% rename from lib/src/utils/matrix_exception.dart rename to lib/matrix_api/model/matrix_exception.dart index e92be43..fa292c9 100644 --- a/lib/src/utils/matrix_exception.dart +++ b/lib/matrix_api/model/matrix_exception.dart @@ -1,24 +1,19 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ 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 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 diff --git a/lib/matrix_api/model/message_types.dart b/lib/matrix_api/model/message_types.dart new file mode 100644 index 0000000..db478bd --- /dev/null +++ b/lib/matrix_api/model/message_types.dart @@ -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 . + */ + +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'; +} diff --git a/lib/matrix_api/model/notifications_query_response.dart b/lib/matrix_api/model/notifications_query_response.dart new file mode 100644 index 0000000..f9eff6b --- /dev/null +++ b/lib/matrix_api/model/notifications_query_response.dart @@ -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 . + */ + +import 'matrix_event.dart'; + +class NotificationsQueryResponse { + String nextToken; + List notifications; + + NotificationsQueryResponse.fromJson(Map json) { + nextToken = json['next_token']; + notifications = []; + json['notifications'].forEach((v) { + notifications.add(Notification.fromJson(v)); + }); + } + + Map toJson() { + final data = {}; + if (nextToken != null) { + data['next_token'] = nextToken; + } + data['notifications'] = notifications.map((v) => v.toJson()).toList(); + return data; + } +} + +class Notification { + List actions; + String profileTag; + bool read; + String roomId; + int ts; + MatrixEvent event; + + Notification.fromJson(Map json) { + actions = json['actions'].cast(); + profileTag = json['profile_tag']; + read = json['read']; + roomId = json['room_id']; + ts = json['ts']; + event = MatrixEvent.fromJson(json['event']); + } + + Map toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/one_time_keys_claim_response.dart b/lib/matrix_api/model/one_time_keys_claim_response.dart new file mode 100644 index 0000000..501fd48 --- /dev/null +++ b/lib/matrix_api/model/one_time_keys_claim_response.dart @@ -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 . + */ + +class OneTimeKeysClaimResponse { + Map failures; + Map> oneTimeKeys; + + OneTimeKeysClaimResponse.fromJson(Map json) { + failures = Map.from(json['failures']); + oneTimeKeys = Map>.from(json['one_time_keys']); + } + + Map toJson() { + final data = {}; + if (failures != null) { + data['failures'] = failures; + } + if (oneTimeKeys != null) { + data['one_time_keys'] = oneTimeKeys; + } + return data; + } +} diff --git a/lib/matrix_api/model/open_graph_data.dart b/lib/matrix_api/model/open_graph_data.dart new file mode 100644 index 0000000..d2d8eb6 --- /dev/null +++ b/lib/matrix_api/model/open_graph_data.dart @@ -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 . + */ + +class OpenGraphData { + String ogTitle; + String ogDescription; + String ogImage; + String ogImageType; + int ogImageHeight; + int ogImageWidth; + int matrixImageSize; + + OpenGraphData.fromJson(Map 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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/open_id_credentials.dart b/lib/matrix_api/model/open_id_credentials.dart new file mode 100644 index 0000000..6d9f6f0 --- /dev/null +++ b/lib/matrix_api/model/open_id_credentials.dart @@ -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 . + */ + +class OpenIdCredentials { + String accessToken; + String tokenType; + String matrixServerName; + int expiresIn; + + OpenIdCredentials.fromJson(Map json) { + accessToken = json['access_token']; + tokenType = json['token_type']; + matrixServerName = json['matrix_server_name']; + expiresIn = json['expires_in']; + } + + Map toJson() { + final data = {}; + data['access_token'] = accessToken; + data['token_type'] = tokenType; + data['matrix_server_name'] = matrixServerName; + data['expires_in'] = expiresIn; + return data; + } +} diff --git a/lib/matrix_api/model/presence.dart b/lib/matrix_api/model/presence.dart new file mode 100644 index 0000000..c804406 --- /dev/null +++ b/lib/matrix_api/model/presence.dart @@ -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 . + */ + +import 'basic_event_with_sender.dart'; +import 'presence_content.dart'; + +class Presence extends BasicEventWithSender { + PresenceContent presence; + + Presence.fromJson(Map json) { + final basicEvent = BasicEventWithSender.fromJson(json); + type = basicEvent.type; + content = basicEvent.content; + senderId = basicEvent.senderId; + presence = PresenceContent.fromJson(content); + } +} diff --git a/lib/matrix_api/model/presence_content.dart b/lib/matrix_api/model/presence_content.dart new file mode 100644 index 0000000..df9d2f0 --- /dev/null +++ b/lib/matrix_api/model/presence_content.dart @@ -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 . + */ + +enum PresenceType { online, offline, unavailable } + +class PresenceContent { + PresenceType presence; + int lastActiveAgo; + String statusMsg; + bool currentlyActive; + + PresenceContent.fromJson(Map 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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/profile.dart b/lib/matrix_api/model/profile.dart new file mode 100644 index 0000000..37eace5 --- /dev/null +++ b/lib/matrix_api/model/profile.dart @@ -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 . + */ + +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 additionalContent; + + Profile(this.displayname, this.avatarUrl, + {this.additionalContent = const {}}); + + Profile.fromJson(Map 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 toJson() { + return additionalContent; + } +} diff --git a/lib/matrix_api/model/public_rooms_response.dart b/lib/matrix_api/model/public_rooms_response.dart new file mode 100644 index 0000000..f227dfb --- /dev/null +++ b/lib/matrix_api/model/public_rooms_response.dart @@ -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 . + */ + +class PublicRoomsResponse { + List chunk; + String nextBatch; + String prevBatch; + int totalRoomCountEstimate; + + PublicRoomsResponse.fromJson(Map json) { + chunk = []; + json['chunk'].forEach((v) { + chunk.add(PublicRoom.fromJson(v)); + }); + nextBatch = json['next_batch']; + prevBatch = json['prev_batch']; + totalRoomCountEstimate = json['total_room_count_estimate']; + } + + Map toJson() { + final data = {}; + 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 aliases; + String avatarUrl; + bool guestCanJoin; + String name; + int numJoinedMembers; + String roomId; + String topic; + bool worldReadable; + String canonicalAlias; + + PublicRoom.fromJson(Map json) { + aliases = json['aliases']?.cast(); + 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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/push_rule_set.dart b/lib/matrix_api/model/push_rule_set.dart new file mode 100644 index 0000000..2c9682b --- /dev/null +++ b/lib/matrix_api/model/push_rule_set.dart @@ -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 . + */ + +enum PushRuleKind { content, override, room, sender, underride } +enum PushRuleAction { notify, dont_notify, coalesce, set_tweak } + +class PushRuleSet { + List content; + List override; + List room; + List sender; + List underride; + + PushRuleSet.fromJson(Map 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 toJson() { + final data = {}; + 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 actions; + List conditions; + bool isDefault; + bool enabled; + String pattern; + String ruleId; + + PushRule.fromJson(Map 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 toJson() { + final data = {}; + 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 json) { + key = json['key']; + kind = json['kind']; + pattern = json['pattern']; + isOperator = json['is']; + } + + Map toJson() { + final data = {}; + if (key != null) { + data['key'] = key; + } + data['kind'] = kind; + if (pattern != null) { + data['pattern'] = pattern; + } + if (isOperator != null) { + data['is'] = isOperator; + } + return data; + } +} diff --git a/lib/matrix_api/model/pusher.dart b/lib/matrix_api/model/pusher.dart new file mode 100644 index 0000000..18ef050 --- /dev/null +++ b/lib/matrix_api/model/pusher.dart @@ -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 . + */ + +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 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 toJson() { + final data = {}; + 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 json) { + url = Uri.parse(json['url']); + format = json['format']; + } + + Map toJson() { + final data = {}; + if (url != null) { + data['url'] = url.toString(); + } + if (format != null) { + data['format'] = format; + } + return data; + } +} diff --git a/lib/matrix_api/model/request_token_response.dart b/lib/matrix_api/model/request_token_response.dart new file mode 100644 index 0000000..ee264be --- /dev/null +++ b/lib/matrix_api/model/request_token_response.dart @@ -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 . + */ + +class RequestTokenResponse { + String sid; + String submitUrl; + + RequestTokenResponse.fromJson(Map json) { + sid = json['sid']; + submitUrl = json['submit_url']; + } + + Map toJson() { + final data = {}; + data['sid'] = sid; + data['submit_url'] = submitUrl; + return data; + } +} diff --git a/lib/matrix_api/model/room_alias_informations.dart b/lib/matrix_api/model/room_alias_informations.dart new file mode 100644 index 0000000..d6b8164 --- /dev/null +++ b/lib/matrix_api/model/room_alias_informations.dart @@ -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 . + */ + +class RoomAliasInformations { + String roomId; + List servers; + + RoomAliasInformations.fromJson(Map json) { + roomId = json['room_id']; + servers = json['servers'].cast(); + } + + Map toJson() { + final data = {}; + data['room_id'] = roomId; + data['servers'] = servers; + return data; + } +} diff --git a/lib/matrix_api/model/room_summary.dart b/lib/matrix_api/model/room_summary.dart new file mode 100644 index 0000000..67b6a8f --- /dev/null +++ b/lib/matrix_api/model/room_summary.dart @@ -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 . + */ + +class RoomSummary { + List mHeroes; + int mJoinedMemberCount; + int mInvitedMemberCount; + RoomSummary.fromJson(Map json) { + mHeroes = + json['m.heroes'] != null ? List.from(json['m.heroes']) : null; + mJoinedMemberCount = json['m.joined_member_count']; + mInvitedMemberCount = json['m.invited_member_count']; + } + Map toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/server_capabilities.dart b/lib/matrix_api/model/server_capabilities.dart new file mode 100644 index 0000000..0e449e9 --- /dev/null +++ b/lib/matrix_api/model/server_capabilities.dart @@ -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 . + */ + +enum RoomVersionStability { stable, unstable } + +class ServerCapabilities { + MChangePassword mChangePassword; + MRoomVersions mRoomVersions; + Map customCapabilities; + + ServerCapabilities.fromJson(Map 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.from(json); + customCapabilities.remove('m.change_password'); + customCapabilities.remove('m.room_versions'); + } + + Map toJson() { + final data = {}; + 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 json) { + enabled = json['enabled']; + } + + Map toJson() { + final data = {}; + data['enabled'] = enabled; + return data; + } +} + +class MRoomVersions { + String defaultVersion; + Map available; + + MRoomVersions.fromJson(Map json) { + defaultVersion = json['default']; + available = (json['available'] as Map).map( + (k, v) => MapEntry( + k, + RoomVersionStability.values + .firstWhere((r) => r.toString().split('.').last == v), + ), + ); + } + + Map toJson() { + final data = {}; + data['default'] = defaultVersion; + data['available'] = available.map( + (k, v) => MapEntry(k, v.toString().split('.').last)); + return data; + } +} diff --git a/lib/matrix_api/model/stripped_state_event.dart b/lib/matrix_api/model/stripped_state_event.dart new file mode 100644 index 0000000..86511a5 --- /dev/null +++ b/lib/matrix_api/model/stripped_state_event.dart @@ -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 . + */ + +import 'package:famedlysdk/matrix_api/model/basic_event_with_sender.dart'; + +class StrippedStateEvent extends BasicEventWithSender { + String stateKey; + + StrippedStateEvent(); + StrippedStateEvent.fromJson(Map json) { + final basicEvent = BasicEventWithSender.fromJson(json); + content = basicEvent.content; + type = basicEvent.type; + senderId = basicEvent.senderId; + stateKey = json['state_key']; + } + + @override + Map toJson() { + final data = super.toJson(); + data['state_key'] = stateKey; + return data; + } +} diff --git a/lib/matrix_api/model/supported_protocol.dart b/lib/matrix_api/model/supported_protocol.dart new file mode 100644 index 0000000..736b7d6 --- /dev/null +++ b/lib/matrix_api/model/supported_protocol.dart @@ -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 . + */ + +class SupportedProtocol { + List userFields; + List locationFields; + String icon; + Map fieldTypes; + List instances; + + SupportedProtocol.fromJson(Map json) { + userFields = json['user_fields'].cast(); + locationFields = json['location_fields'].cast(); + icon = json['icon']; + fieldTypes = (json['field_types'] as Map) + .map((k, v) => MapEntry(k, ProtocolFieldType.fromJson(v))); + instances = []; + json['instances'].forEach((v) { + instances.add(ProtocolInstance.fromJson(v)); + }); + } + + Map toJson() { + final data = {}; + 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 json) { + regexp = json['regexp']; + placeholder = json['placeholder']; + } + + Map toJson() { + final data = {}; + data['regexp'] = regexp; + data['placeholder'] = placeholder; + return data; + } +} + +class ProtocolInstance { + String networkId; + String desc; + String icon; + dynamic fields; + + ProtocolInstance.fromJson(Map json) { + networkId = json['network_id']; + desc = json['desc']; + icon = json['icon']; + fields = json['fields']; + } + + Map toJson() { + final data = {}; + data['network_id'] = networkId; + data['desc'] = desc; + if (icon != null) { + data['icon'] = icon; + } + data['fields'] = fields; + + return data; + } +} diff --git a/lib/matrix_api/model/supported_versions.dart b/lib/matrix_api/model/supported_versions.dart new file mode 100644 index 0000000..e5beaf3 --- /dev/null +++ b/lib/matrix_api/model/supported_versions.dart @@ -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 . + */ + +class SupportedVersions { + List versions; + Map unstableFeatures; + + SupportedVersions.fromJson(Map json) { + versions = json['versions'].cast(); + unstableFeatures = Map.from(json['unstable_features']); + } + + Map toJson() { + final data = {}; + data['versions'] = versions; + if (unstableFeatures != null) { + data['unstable_features'] = unstableFeatures; + } + return data; + } +} diff --git a/lib/matrix_api/model/sync_update.dart b/lib/matrix_api/model/sync_update.dart new file mode 100644 index 0000000..f82ab04 --- /dev/null +++ b/lib/matrix_api/model/sync_update.dart @@ -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 . + */ + +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; + List accountData; + List toDevice; + DeviceListsUpdate deviceLists; + Map deviceOneTimeKeysCount; + + SyncUpdate.fromJson(Map 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.from(json['device_one_time_keys_count']) + : null; + } + + Map toJson() { + final data = {}; + 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 join; + Map invite; + Map leave; + + RoomsUpdate.fromJson(Map 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 toJson() { + final data = {}; + 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 state; + TimelineUpdate timeline; + List ephemeral; + List accountData; + UnreadNotificationCounts unreadNotifications; + + JoinedRoomUpdate.fromJson(Map 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 toJson() { + final data = {}; + 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 inviteState; + InvitedRoomUpdate.fromJson(Map 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 toJson() { + final data = {}; + if (inviteState != null) { + data['invite_state'] = { + 'events': inviteState.map((i) => i.toJson()).toList(), + }; + } + return data; + } +} + +class LeftRoomUpdate extends SyncRoomUpdate { + List state; + TimelineUpdate timeline; + List accountData; + + LeftRoomUpdate.fromJson(Map 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 toJson() { + final data = {}; + 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 events; + bool limited; + String prevBatch; + TimelineUpdate.fromJson(Map json) { + events = json['events'] != null + ? (json['events'] as List).map((i) => MatrixEvent.fromJson(i)).toList() + : null; + limited = json['limited']; + prevBatch = json['prev_batch']; + } + Map toJson() { + final data = {}; + 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 json) { + highlightCount = json['highlight_count']; + notificationCount = json['notification_count']; + } + Map toJson() { + final data = {}; + if (highlightCount != null) { + data['highlight_count'] = highlightCount; + } + if (notificationCount != null) { + data['notification_count'] = notificationCount; + } + return data; + } +} + +class DeviceListsUpdate { + List changed; + List left; + DeviceListsUpdate.fromJson(Map json) { + changed = List.from(json['changed']); + left = List.from(json['left']); + } + Map toJson() { + final data = {}; + if (changed != null) { + data['changed'] = changed; + } + if (left != null) { + data['left'] = left; + } + return data; + } +} diff --git a/lib/matrix_api/model/tag.dart b/lib/matrix_api/model/tag.dart new file mode 100644 index 0000000..fa27ea9 --- /dev/null +++ b/lib/matrix_api/model/tag.dart @@ -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 . + */ + +class Tag { + double order; + + Tag.fromJson(Map json) { + order = json['order']; + } + + Map toJson() { + final data = {}; + if (order != null) { + data['order'] = order; + } + return data; + } +} diff --git a/lib/matrix_api/model/third_party_identifier.dart b/lib/matrix_api/model/third_party_identifier.dart new file mode 100644 index 0000000..dedd617 --- /dev/null +++ b/lib/matrix_api/model/third_party_identifier.dart @@ -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 . + */ + +class ThirdPartyIdentifier { + String medium; + String address; + int validatedAt; + int addedAt; + + ThirdPartyIdentifier.fromJson(Map json) { + medium = json['medium']; + address = json['address']; + validatedAt = json['validated_at']; + addedAt = json['added_at']; + } + + Map toJson() { + final data = {}; + data['medium'] = medium; + data['address'] = address; + data['validated_at'] = validatedAt; + data['added_at'] = addedAt; + return data; + } +} diff --git a/lib/matrix_api/model/third_party_location.dart b/lib/matrix_api/model/third_party_location.dart new file mode 100644 index 0000000..6808380 --- /dev/null +++ b/lib/matrix_api/model/third_party_location.dart @@ -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 . + */ + +class ThirdPartyLocation { + String alias; + String protocol; + Map fields; + + ThirdPartyLocation.fromJson(Map json) { + alias = json['alias']; + protocol = json['protocol']; + fields = Map.from(json['fields']); + } + + Map toJson() { + final data = {}; + data['alias'] = alias; + data['protocol'] = protocol; + data['fields'] = fields; + return data; + } +} diff --git a/lib/matrix_api/model/third_party_user.dart b/lib/matrix_api/model/third_party_user.dart new file mode 100644 index 0000000..fd84f90 --- /dev/null +++ b/lib/matrix_api/model/third_party_user.dart @@ -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 . + */ + +class ThirdPartyUser { + String userId; + String protocol; + Map fields; + + ThirdPartyUser.fromJson(Map json) { + userId = json['userid']; + protocol = json['protocol']; + fields = Map.from(json['fields']); + } + + Map toJson() { + final data = {}; + data['userid'] = userId; + data['protocol'] = protocol; + data['fields'] = fields; + return data; + } +} diff --git a/lib/matrix_api/model/timeline_history_response.dart b/lib/matrix_api/model/timeline_history_response.dart new file mode 100644 index 0000000..e575276 --- /dev/null +++ b/lib/matrix_api/model/timeline_history_response.dart @@ -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 . + */ + +import 'matrix_event.dart'; + +class TimelineHistoryResponse { + String start; + String end; + List chunk; + List state; + + TimelineHistoryResponse.fromJson(Map 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 toJson() { + final data = {}; + 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; + } +} diff --git a/lib/matrix_api/model/turn_server_credentials.dart b/lib/matrix_api/model/turn_server_credentials.dart new file mode 100644 index 0000000..d7c4520 --- /dev/null +++ b/lib/matrix_api/model/turn_server_credentials.dart @@ -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 . + */ + +class TurnServerCredentials { + String username; + String password; + List uris; + int ttl; + + TurnServerCredentials.fromJson(Map json) { + username = json['username']; + password = json['password']; + uris = json['uris'].cast(); + ttl = json['ttl']; + } + + Map toJson() { + final data = {}; + data['username'] = username; + data['password'] = password; + data['uris'] = uris; + data['ttl'] = ttl; + return data; + } +} diff --git a/lib/matrix_api/model/user_search_result.dart b/lib/matrix_api/model/user_search_result.dart new file mode 100644 index 0000000..1d49808 --- /dev/null +++ b/lib/matrix_api/model/user_search_result.dart @@ -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 . + */ + +import 'profile.dart'; + +class UserSearchResult { + List results; + bool limited; + + UserSearchResult.fromJson(Map json) { + results = []; + json['results'].forEach((v) { + results.add(Profile.fromJson(v)); + }); + + limited = json['limited']; + } + + Map toJson() { + final data = {}; + data['results'] = results.map((v) => v.toJson()).toList(); + + data['limited'] = limited; + return data; + } +} diff --git a/lib/matrix_api/model/well_known_informations.dart b/lib/matrix_api/model/well_known_informations.dart new file mode 100644 index 0000000..feba960 --- /dev/null +++ b/lib/matrix_api/model/well_known_informations.dart @@ -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 . + */ + +class WellKnownInformations { + MHomeserver mHomeserver; + MHomeserver mIdentityServer; + Map content; + + WellKnownInformations.fromJson(Map 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 toJson() { + final data = content; + data['m.homeserver'] = mHomeserver.toJson(); + data['m.identity_server'] = mIdentityServer.toJson(); + return data; + } +} + +class MHomeserver { + String baseUrl; + + MHomeserver.fromJson(Map json) { + baseUrl = json['base_url']; + } + + Map toJson() { + final data = {}; + data['base_url'] = baseUrl; + return data; + } +} diff --git a/lib/matrix_api/model/who_is_info.dart b/lib/matrix_api/model/who_is_info.dart new file mode 100644 index 0000000..6a26685 --- /dev/null +++ b/lib/matrix_api/model/who_is_info.dart @@ -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 . + */ + +class WhoIsInfo { + String userId; + Map devices; + + WhoIsInfo.fromJson(Map json) { + userId = json['user_id']; + devices = json['devices'] != null + ? (json['devices'] as Map) + .map((k, v) => MapEntry(k, DeviceInfo.fromJson(v))) + : null; + } + + Map toJson() { + final data = {}; + data['user_id'] = userId; + if (devices != null) { + data['devices'] = devices.map((k, v) => MapEntry(k, v.toJson())); + } + return data; + } +} + +class DeviceInfo { + List sessions; + + DeviceInfo.fromJson(Map json) { + if (json['sessions'] != null) { + sessions = []; + json['sessions'].forEach((v) { + sessions.add(Sessions.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (sessions != null) { + data['sessions'] = sessions.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Sessions { + List connections; + + Sessions.fromJson(Map json) { + if (json['connections'] != null) { + connections = []; + json['connections'].forEach((v) { + connections.add(Connections.fromJson(v)); + }); + } + } + + Map toJson() { + final data = {}; + if (connections != null) { + data['connections'] = connections.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Connections { + String ip; + int lastSeen; + String userAgent; + + Connections.fromJson(Map json) { + ip = json['ip']; + lastSeen = json['last_seen']; + userAgent = json['user_agent']; + } + + Map toJson() { + final data = {}; + if (ip != null) { + data['ip'] = ip; + } + if (lastSeen != null) { + data['last_seen'] = lastSeen; + } + if (userAgent != null) { + data['user_agent'] = userAgent; + } + return data; + } +} diff --git a/lib/src/account_data.dart b/lib/src/account_data.dart deleted file mode 100644 index 7b74162..0000000 --- a/lib/src/account_data.dart +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -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 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 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); - } -} diff --git a/lib/src/client.dart b/lib/src/client.dart index 3782939..114bfca 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1,24 +1,19 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:async'; @@ -27,51 +22,33 @@ import 'dart:core'; import 'package:canonical_json/canonical_json.dart'; import 'package:famedlysdk/famedlysdk.dart'; -import 'package:famedlysdk/src/account_data.dart'; -import 'package:famedlysdk/src/presence.dart'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/src/room.dart'; -import 'package:famedlysdk/src/sync/user_update.dart'; import 'package:famedlysdk/src/utils/device_keys_list.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart'; -import 'package:famedlysdk/src/utils/open_id_credentials.dart'; -import 'package:famedlysdk/src/utils/public_rooms_response.dart'; import 'package:famedlysdk/src/utils/session_key.dart'; import 'package:famedlysdk/src/utils/to_device_event.dart'; -import 'package:famedlysdk/src/utils/turn_server_credentials.dart'; -import 'package:famedlysdk/src/utils/user_device.dart'; import 'package:http/http.dart' as http; -import 'package:mime_type/mime_type.dart'; import 'package:olm/olm.dart' as olm; import 'package:pedantic/pedantic.dart'; import 'event.dart'; import 'room.dart'; -import 'sync/event_update.dart'; -import 'sync/room_update.dart'; -import 'sync/user_update.dart'; +import 'utils/event_update.dart'; +import 'utils/room_update.dart'; import 'user.dart'; -import 'utils/matrix_exception.dart'; -import 'utils/profile.dart'; import 'database/database.dart' show Database; -import 'utils/pusher.dart'; -import 'utils/well_known_informations.dart'; import 'utils/key_verification.dart'; import 'key_manager.dart'; typedef RoomSorter = int Function(Room a, Room b); -enum HTTPType { GET, POST, PUT, DELETE } - 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 { - /// Handles the connection for this client. - @deprecated - Client get connection => this; - int _id; int get id => _id; @@ -80,16 +57,24 @@ class Client { bool enableE2eeRecovery; + MatrixApi api; + /// Create a client /// clientName = unique identifier of this client /// debug: Print debug output? /// database: The database instance to use /// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions Client(this.clientName, - {this.debug = false, this.database, this.enableE2eeRecovery = false}) { + {this.debug = false, + this.database, + this.enableE2eeRecovery = false, + http.Client httpClient}) { + api = MatrixApi(debug: debug, httpClient: httpClient); keyManager = KeyManager(this); onLoginStateChanged.stream.listen((loginState) { - print('LoginState: ${loginState.toString()}'); + if (debug) { + print('[LoginState]: ${loginState.toString()}'); + } }); } @@ -99,19 +84,10 @@ class Client { /// The required name for this client. final String clientName; - /// The homeserver this client is communicating with. - String get homeserver => _homeserver; - String _homeserver; - /// The Matrix ID of the current logged user. String get userID => _userID; String _userID; - /// This is the access token for the matrix client. When it is undefined, then - /// the user needs to sign in first. - String get accessToken => _accessToken; - String _accessToken; - /// This points to the position in the synchronization history. String prevBatch; @@ -124,7 +100,7 @@ class Client { String _deviceName; /// Returns the current login state. - bool isLogged() => accessToken != null; + bool isLogged() => api.accessToken != null; /// A list of all rooms the user is participating or invited. List get rooms => _rooms; @@ -150,14 +126,28 @@ class Client { } /// Key/Value store of account data. - Map accountData = {}; + Map accountData = {}; /// Presences of users by a given matrix ID Map presences = {}; - int _timeoutFactor = 1; - 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}'; @@ -177,19 +167,6 @@ class Client { return null; } - void handleUserUpdate(UserUpdate userUpdate) { - if (userUpdate.type == 'account_data') { - var newAccountData = AccountData.fromJson(userUpdate.content); - accountData[newAccountData.typeKey] = newAccountData; - if (onAccountData != null) onAccountData.add(newAccountData); - } - if (userUpdate.type == 'presence') { - var newPresence = Presence.fromJson(userUpdate.content); - presences[newPresence.sender] = newPresence; - if (onPresence != null) onPresence.add(newPresence); - } - } - Map get directChats => accountData['m.direct'] != null ? accountData['m.direct'].content : {}; @@ -204,10 +181,7 @@ class Client { } (accountData['m.direct'].content[userId] as List) .remove(accountData['m.direct'].content[userId][0]); - jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/user/${userID}/account_data/m.direct', - data: directChats); + api.setAccountData(userId, 'm.direct', directChats); return getDirectChatFromUserId(userId); } for (var i = 0; i < rooms.length; i++) { @@ -234,56 +208,37 @@ class Client { /// login types. Returns false if the server is not compatible with the /// client. /// Throws FormatException, TimeoutException and MatrixException on error. - Future checkServer(serverUrl) async { + Future checkServer(dynamic serverUrl) async { try { - _homeserver = serverUrl; - final versionResp = - await jsonRequest(type: HTTPType.GET, action: '/client/versions'); + api.homeserver = (serverUrl is Uri) ? serverUrl : Uri.parse(serverUrl); + final versions = await api.requestSupportedVersions(); - final versions = List.from(versionResp['versions']); - - for (var i = 0; i < versions.length; i++) { - if (versions[i] == 'r0.5.0' || versions[i] == 'r0.6.0') { + for (var i = 0; i < versions.versions.length; i++) { + if (versions.versions[i] == 'r0.5.0' || + versions.versions[i] == 'r0.6.0') { break; - } else if (i == versions.length - 1) { + } else if (i == versions.versions.length - 1) { return false; } } - final loginResp = - await jsonRequest(type: HTTPType.GET, action: '/client/r0/login'); - - final List flows = loginResp['flows']; - - for (var i = 0; i < flows.length; i++) { - if (flows[i].containsKey('type') && - flows[i]['type'] == 'm.login.password') { - break; - } else if (i == flows.length - 1) { - return false; - } + final loginTypes = await api.requestLoginTypes(); + if (loginTypes.flows.indexWhere((f) => f.type == 'm.login.password') == + -1) { + return false; } + return true; } catch (_) { - _homeserver = null; + api.homeserver = null; rethrow; } } - /// Checks to see if a username is available, and valid, for the server. - /// You have to call [checkServer] first to set a homeserver. - Future usernameAvailable(String username) async { - final response = await jsonRequest( - type: HTTPType.GET, - action: '/client/r0/register/available?username=$username', - ); - return response['available']; - } - /// 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({ + Future register({ String kind, String username, String password, @@ -292,31 +247,28 @@ class Client { String initialDeviceDisplayName, bool inhibitLogin, }) async { - final action = '/client/r0/register' + (kind != null ? '?kind=$kind' : ''); - var data = {}; - if (username != null) data['username'] = username; - if (password != null) data['password'] = password; - if (auth != null) data['auth'] = auth; - if (deviceId != null) data['device_id'] = deviceId; - if (initialDeviceDisplayName != null) { - data['initial_device_display_name'] = initialDeviceDisplayName; - } - if (inhibitLogin != null) data['inhibit_login'] = inhibitLogin; - final response = - await jsonRequest(type: HTTPType.POST, action: action, data: data); + final response = await api.register( + username: username, + password: password, + auth: auth, + deviceId: deviceId, + initialDeviceDisplayName: initialDeviceDisplayName, + inhibitLogin: inhibitLogin, + ); // Connect if there is an access token in the response. - if (response.containsKey('access_token') && - response.containsKey('device_id') && - response.containsKey('user_id')) { - await connect( - newToken: response['access_token'], - newUserID: response['user_id'], - newHomeserver: homeserver, - newDeviceName: initialDeviceDisplayName ?? '', - newDeviceID: response['device_id']); + if (response.accessToken == null || + response.deviceId == null || + response.userId == null) { + throw 'Registered but token, device ID or user ID is null.'; } - return response; + await connect( + newToken: response.accessToken, + newUserID: response.userId, + newHomeserver: api.homeserver, + newDeviceName: initialDeviceDisplayName ?? '', + newDeviceID: response.deviceId); + return; } /// Handles the login and allows the client to call all APIs which require @@ -343,30 +295,38 @@ class Client { data['initial_device_display_name'] = initialDeviceDisplayName; } - final loginResp = await jsonRequest( - type: HTTPType.POST, action: '/client/r0/login', data: data); + final loginResp = await api.login( + type: 'm.login.password', + userIdentifierType: 'm.id.user', + user: username, + password: password, + deviceId: deviceId, + initialDeviceDisplayName: initialDeviceDisplayName, + ); - if (loginResp.containsKey('user_id') && - loginResp.containsKey('access_token') && - loginResp.containsKey('device_id')) { - await connect( - newToken: loginResp['access_token'], - newUserID: loginResp['user_id'], - newHomeserver: homeserver, - newDeviceName: initialDeviceDisplayName ?? '', - newDeviceID: loginResp['device_id'], - ); - return true; + // 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.'; } - return false; + await connect( + newToken: loginResp.accessToken, + newUserID: loginResp.userId, + newHomeserver: api.homeserver, + newDeviceName: initialDeviceDisplayName ?? '', + newDeviceID: loginResp.deviceId, + ); + return true; } /// Sends a logout command to the homeserver and clears all local data, /// including all persistent data from the store. Future logout() async { try { - await jsonRequest(type: HTTPType.POST, action: '/client/r0/logout'); + await api.logout(); } catch (exception) { + print(exception); rethrow; } finally { await clear(); @@ -417,45 +377,37 @@ class Client { if (cache && _profileCache.containsKey(userId)) { return _profileCache[userId]; } - final resp = await jsonRequest( - type: HTTPType.GET, action: '/client/r0/profile/${userId}'); - final profile = Profile.fromJson(resp); + final profile = await api.requestProfile(userId); _profileCache[userId] = profile; return profile; } Future> get archive async { var archiveList = []; - var syncFilters = '{"room":{"include_leave":true,"timeline":{"limit":10}}}'; - var action = '/client/r0/sync?filter=$syncFilters&timeout=0'; - final sync = await jsonRequest(type: HTTPType.GET, action: action); - if (sync['rooms']['leave'] is Map) { - for (var entry in sync['rooms']['leave'].entries) { - final String id = entry.key; - final dynamic room = entry.value; + final sync = await api.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) { + final id = entry.key; + final room = entry.value; var leftRoom = Room( id: id, membership: Membership.leave, client: this, - roomAccountData: {}, + roomAccountData: + room.accountData?.asMap()?.map((k, v) => MapEntry(v.type, v)) ?? + {}, mHeroes: []); - if (room['account_data'] is Map && - room['account_data']['events'] is List) { - for (dynamic event in room['account_data']['events']) { - leftRoom.roomAccountData[event['type']] = - RoomAccountData.fromJson(event, leftRoom); + if (room.timeline?.events != null) { + for (var event in room.timeline.events) { + leftRoom.setState(Event.fromMatrixEvent(event, leftRoom)); } } - if (room['timeline'] is Map && - room['timeline']['events'] is List) { - for (dynamic event in room['timeline']['events']) { - leftRoom.setState(Event.fromJson(event, leftRoom)); - } - } - if (room['state'] is Map && - room['state']['events'] is List) { - for (dynamic event in room['state']['events']) { - leftRoom.setState(Event.fromJson(event, leftRoom)); + if (room.state != null) { + for (var event in room.state) { + leftRoom.setState(Event.fromMatrixEvent(event, leftRoom)); } } archiveList.add(leftRoom); @@ -464,14 +416,6 @@ class Client { return archiveList; } - /// This API starts a user participating in a particular room, if that user is allowed to participate in that room. - /// After this call, the client is allowed to see all current state events in the room, and all subsequent events - /// associated with the room until the user leaves the room. - Future joinRoomById(String roomIdOrAlias) async { - return await jsonRequest( - type: HTTPType.POST, action: '/client/r0/join/$roomIdOrAlias'); - } - /// Loads the contact list for this user excluding the user itself. /// Currently the contacts are found by discovering the contacts of /// the famedlyContactDiscovery room, which is @@ -495,119 +439,22 @@ class Client { return contacts; } - @Deprecated('Please use [createRoom] instead!') - Future createGroup(List users) => createRoom(invite: users); - - /// Creates a new group chat and invites the given Users and returns the new - /// created room ID. If [params] are provided, invite will be ignored. For the - /// moment please look at https://matrix.org/docs/spec/client_server/r0.5.0#post-matrix-client-r0-createroom - /// to configure [params]. - Future createRoom( - {List invite, Map params}) async { - var inviteIDs = []; - if (params == null && invite != null) { - for (var i = 0; i < invite.length; i++) { - inviteIDs.add(invite[i].id); - } - } - - try { - final dynamic resp = await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/createRoom', - data: params ?? - { - 'invite': inviteIDs, - }); - return resp['room_id']; - } catch (e) { - rethrow; - } - } - /// Changes the user's displayname. - Future setDisplayname(String displayname) async { - await jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/profile/$userID/displayname', - data: {'displayname': displayname}); - return; - } + Future setDisplayname(String displayname) => + api.setDisplayname(userID, displayname); /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { - final uploadResp = await upload(file); - await jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/profile/$userID/avatar_url', - data: {'avatar_url': uploadResp}); + final uploadResp = await api.upload(file.bytes, file.path); + await api.setAvatarUrl(userID, Uri.parse(uploadResp)); return; } - /// Get credentials for the client to use when initiating calls. - Future getTurnServerCredentials() async { - final response = await jsonRequest( - type: HTTPType.GET, - action: '/client/r0/voip/turnServer', - ); - return TurnServerCredentials.fromJson(response); - } - - /// Fetches the pushrules for the logged in user. - /// These are needed for notifications on Android - @Deprecated('Use [pushRules] instead.') - Future getPushrules() async { - final dynamic resp = await jsonRequest( - type: HTTPType.GET, - action: '/client/r0/pushrules/', - ); - - return PushRules.fromJson(resp); - } - /// Returns the push rules for the logged in user. - PushRules get pushRules => accountData.containsKey('m.push_rules') - ? PushRules.fromJson(accountData['m.push_rules'].content) + PushRuleSet get pushRules => accountData.containsKey('m.push_rules') + ? PushRuleSet.fromJson(accountData['m.push_rules'].content) : null; - /// Gets all currently active pushers for the authenticated user. - Future> getPushers() async { - final response = - await jsonRequest(type: HTTPType.GET, action: '/client/r0/pushers'); - var list = []; - for (final pusherJson in response['pushers']) { - list.add(Pusher.fromJson(pusherJson)); - } - return list; - } - - /// This endpoint allows the creation, modification and deletion of pushers for this user ID. - Future setPushers(String pushKey, String kind, String appId, - String appDisplayName, String deviceDisplayName, String lang, String url, - {bool append, String profileTag, String format}) async { - var data = { - 'lang': lang, - 'kind': kind, - 'app_display_name': appDisplayName, - 'device_display_name': deviceDisplayName, - 'profile_tag': profileTag, - 'app_id': appId, - 'pushkey': pushKey, - 'data': {'url': url} - }; - - if (format != null) data['data']['format'] = format; - if (profileTag != null) data['profile_tag'] = profileTag; - if (append != null) data['append'] = append; - - await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/pushers/set', - data: data, - ); - return; - } - static String syncFilters = '{"room":{"state":{"lazy_load_members":true}}}'; static String messagesFilters = '{"lazy_load_members":true}'; static const List supportedDirectEncryptionAlgorithms = [ @@ -618,8 +465,6 @@ class Client { ]; static const int defaultThumbnailSize = 256; - http.Client httpClient = http.Client(); - /// The newEvent signal is the most important signal in this concept. Every time /// the app receives a new synchronization, this event is called for every signal /// to update the GUI. For example, for a new message, it is called: @@ -631,9 +476,6 @@ class Client { final StreamController onRoomUpdate = StreamController.broadcast(); - /// Outside of rooms there are account updates like account_data or presences. - final StreamController onUserEvent = StreamController.broadcast(); - /// The onToDeviceEvent is called when there comes a new to device event. It is /// already decrypted if necessary. final StreamController onToDeviceEvent = @@ -658,13 +500,13 @@ class Client { final StreamController onFirstSync = StreamController.broadcast(); /// When a new sync response is coming in, this gives the complete payload. - final StreamController onSync = StreamController.broadcast(); + final StreamController onSync = StreamController.broadcast(); /// Callback will be called on presences. final StreamController onPresence = StreamController.broadcast(); /// Callback will be called on account data updates. - final StreamController onAccountData = + final StreamController onAccountData = StreamController.broadcast(); /// Will be called on call invites. @@ -710,7 +552,7 @@ class Client { /// /// ``` /// final resp = await matrix - /// .jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: { + /// .jsonRequest(type: RequestType.POST, action: "/client/r0/login", data: { /// "type": "m.login.password", /// "user": "test", /// "password": "1234", @@ -731,7 +573,7 @@ class Client { /// Sends [LoginState.logged] to [onLoginStateChanged]. void connect({ String newToken, - String newHomeserver, + Uri newHomeserver, String newUserID, String newDeviceName, String newDeviceID, @@ -743,8 +585,8 @@ class Client { final account = await database.getClient(clientName); if (account != null) { _id = account.clientId; - _homeserver = account.homeserverUrl; - _accessToken = account.token; + api.homeserver = Uri.parse(account.homeserverUrl); + api.accessToken = account.token; _userID = account.userId; _deviceID = account.deviceId; _deviceName = account.deviceName; @@ -752,15 +594,15 @@ class Client { olmAccount = account.olmAccount; } } - _accessToken = newToken ?? _accessToken; - _homeserver = newHomeserver ?? _homeserver; + api.accessToken = newToken ?? api.accessToken; + api.homeserver = newHomeserver ?? api.homeserver; _userID = newUserID ?? _userID; _deviceID = newDeviceID ?? _deviceID; _deviceName = newDeviceName ?? _deviceName; prevBatch = newPrevBatch ?? prevBatch; olmAccount = newOlmAccount ?? olmAccount; - if (_accessToken == null || _homeserver == null || _userID == null) { + if (api.accessToken == null || api.homeserver == null || _userID == null) { // we aren't logged in onLoginStateChanged.add(LoginState.loggedOut); return; @@ -791,8 +633,8 @@ class Client { if (database != null) { if (id != null) { await database.updateClient( - _homeserver, - _accessToken, + api.homeserver.toString(), + api.accessToken, _userID, _deviceID, _deviceName, @@ -803,8 +645,8 @@ class Client { } else { _id = await database.insertClient( clientName, - _homeserver, - _accessToken, + api.homeserver.toString(), + api.accessToken, _userID, _deviceID, _deviceName, @@ -820,8 +662,6 @@ class Client { presences = await database.getPresences(id); } - _userEventSub ??= onUserEvent.stream.listen(handleUserUpdate); - onLoginStateChanged.add(LoginState.logged); return _sync(); @@ -832,8 +672,6 @@ class Client { _userID = s; } - StreamSubscription _userEventSub; - /// Resets all settings and stops the synchronisation. void clear() { olmSessions.values.forEach((List sessions) { @@ -847,178 +685,31 @@ class Client { }); _olmAccount?.free(); database?.clear(id); - _id = _accessToken = - _homeserver = _userID = _deviceID = _deviceName = prevBatch = null; + _id = api.accessToken = + api.homeserver = _userID = _deviceID = _deviceName = prevBatch = null; _rooms = []; onLoginStateChanged.add(LoginState.loggedOut); } - /// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.4.0.html). - /// - /// Throws: TimeoutException, FormatException, MatrixException - /// - /// You must first call [this.connect()] or set [this.homeserver] before you can use - /// this! For example to send a message to a Matrix room with the id - /// '!fjd823j:example.com' you call: - /// - /// ``` - /// final resp = await jsonRequest( - /// type: HTTPType.PUT, - /// action: "/r0/rooms/!fjd823j:example.com/send/m.room.message/$txnId", - /// data: { - /// "msgtype": "m.text", - /// "body": "hello" - /// } - /// ); - /// ``` - /// - Future> jsonRequest( - {HTTPType type, - String action, - dynamic data = '', - int timeout, - String contentType = 'application/json'}) async { - if (isLogged() == false && homeserver == null) { - throw ('No homeserver specified.'); - } - timeout ??= (_timeoutFactor * syncTimeoutSec) + 5; - dynamic json; - if (data is Map) data.removeWhere((k, v) => v == null); - (!(data is String)) ? json = jsonEncode(data) : json = data; - if (data is List || action.startsWith('/media/r0/upload')) json = data; - - final url = '${homeserver}/_matrix${action}'; - - var headers = {}; - if (type == HTTPType.PUT || type == HTTPType.POST) { - headers['Content-Type'] = contentType; - } - if (isLogged()) { - headers['Authorization'] = 'Bearer ${accessToken}'; - } - - if (debug) { - print( - "[REQUEST ${type.toString().split('.').last}] Action: $action, Data: ${jsonEncode(data)}"); - } - - http.Response resp; - var jsonResp = {}; - try { - switch (type.toString().split('.').last) { - case 'GET': - resp = await httpClient.get(url, headers: headers).timeout( - Duration(seconds: timeout), - onTimeout: () => null, - ); - break; - case 'POST': - resp = - await httpClient.post(url, body: json, headers: headers).timeout( - Duration(seconds: timeout), - onTimeout: () => null, - ); - break; - case 'PUT': - resp = - await httpClient.put(url, body: json, headers: headers).timeout( - Duration(seconds: timeout), - onTimeout: () => null, - ); - break; - case 'DELETE': - resp = await httpClient.delete(url, headers: headers).timeout( - Duration(seconds: timeout), - onTimeout: () => null, - ); - break; - } - if (resp == null) { - throw TimeoutException; - } - jsonResp = jsonDecode(String.fromCharCodes(resp.body.runes)) - as Map; // May throw FormatException - - if (resp.statusCode >= 400 && resp.statusCode < 500) { - // The server has responsed with an matrix related error. - var exception = MatrixException(resp); - if (exception.error == MatrixError.M_UNKNOWN_TOKEN) { - // The token is no longer valid. Need to sign off.... - // TODO: add a way to export keys prior logout? - onError.add(exception); - clear(); - } - - throw exception; - } - - if (debug) print('[RESPONSE] ${jsonResp.toString()}'); - } on ArgumentError catch (exception) { - print(exception); - // Ignore this error - } on TimeoutException catch (_) { - _timeoutFactor *= 2; - rethrow; - } catch (_) { - print(_); - rethrow; - } - - return jsonResp; - } - - /// Uploads a file with the name [fileName] as base64 encoded to the server - /// and returns the mxc url as a string. - Future upload(MatrixFile file, {String contentType}) async { - // For testing - if (homeserver.toLowerCase() == 'https://fakeserver.notexisting') { - return 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'; - } - var headers = {}; - headers['Authorization'] = 'Bearer $accessToken'; - headers['Content-Type'] = contentType ?? mime(file.path); - var fileName = Uri.encodeFull(file.path.split('/').last.toLowerCase()); - final url = '$homeserver/_matrix/media/r0/upload?filename=$fileName'; - final streamedRequest = http.StreamedRequest('POST', Uri.parse(url)) - ..headers.addAll(headers); - streamedRequest.contentLength = await file.bytes.length; - streamedRequest.sink.add(file.bytes); - streamedRequest.sink.close(); - print('[UPLOADING] $fileName'); - var streamedResponse = await streamedRequest.send(); - Map jsonResponse = json.decode( - String.fromCharCodes(await streamedResponse.stream.first), - ); - if (!(jsonResponse['content_uri'] is String && - jsonResponse['content_uri'].isNotEmpty)) { - throw ("Missing json key: 'content_uri' ${jsonResponse.toString()}"); - } - return jsonResponse['content_uri']; - } - - Future _syncRequest; + Future _syncRequest; Future _sync() async { if (isLogged() == false || _disposed) return; - - var action = '/client/r0/sync?filter=$syncFilters'; - - if (prevBatch != null) { - action += '&timeout=30000'; - action += '&since=${prevBatch}'; - } try { - _syncRequest = jsonRequest(type: HTTPType.GET, action: action); + _syncRequest = api.sync( + filter: syncFilters, + since: prevBatch, + timeout: prevBatch != null ? 30000 : null, + ); if (_disposed) return; final hash = _syncRequest.hashCode; final syncResp = await _syncRequest; if (hash != _syncRequest.hashCode) return; - _timeoutFactor = 1; if (database != null) { await database.transaction(() async { await handleSync(syncResp); - if (prevBatch != syncResp['next_batch']) { - await database.storePrevBatch(syncResp['next_batch'], id); + if (prevBatch != syncResp.nextBatch) { + await database.storePrevBatch(syncResp.nextBatch, id); } }); } else { @@ -1027,10 +718,10 @@ class Client { if (_disposed) return; if (prevBatch == null) { onFirstSync.add(true); - prevBatch = syncResp['next_batch']; + prevBatch = syncResp.nextBatch; _sortRooms(); } - prevBatch = syncResp['next_batch']; + prevBatch = syncResp.nextBatch; await _updateUserDeviceKeys(); _cleanupKeyVerificationRequests(); if (hash == _syncRequest.hashCode) unawaited(_sync()); @@ -1047,35 +738,54 @@ class Client { } /// Use this method only for testing utilities! - Future handleSync(dynamic sync) async { - if (sync['to_device'] is Map && - sync['to_device']['events'] is List) { - _handleToDeviceEvents(sync['to_device']['events']); + Future handleSync(SyncUpdate sync) async { + if (sync.toDevice != null) { + _handleToDeviceEvents(sync.toDevice); } - if (sync['rooms'] is Map) { - if (sync['rooms']['join'] is Map) { - await _handleRooms(sync['rooms']['join'], Membership.join); + if (sync.rooms != null) { + if (sync.rooms.join != null) { + await _handleRooms(sync.rooms.join, Membership.join); } - if (sync['rooms']['invite'] is Map) { - await _handleRooms(sync['rooms']['invite'], Membership.invite); + if (sync.rooms.invite != null) { + await _handleRooms(sync.rooms.invite, Membership.invite); } - if (sync['rooms']['leave'] is Map) { - await _handleRooms(sync['rooms']['leave'], Membership.leave); + if (sync.rooms.leave != null) { + await _handleRooms(sync.rooms.leave, Membership.leave); } } - if (sync['presence'] is Map && - sync['presence']['events'] is List) { - await _handleGlobalEvents(sync['presence']['events'], 'presence'); + if (sync.presence != null) { + for (final newPresence in sync.presence) { + if (database != null) { + await database.storeUserEventUpdate( + id, + 'presence', + newPresence.type, + newPresence.toJson(), + ); + } + presences[newPresence.senderId] = newPresence; + onPresence.add(newPresence); + } } - if (sync['account_data'] is Map && - sync['account_data']['events'] is List) { - await _handleGlobalEvents(sync['account_data']['events'], 'account_data'); + if (sync.accountData != null) { + for (final newAccountData in sync.accountData) { + if (database != null) { + await database.storeUserEventUpdate( + id, + 'account_data', + newAccountData.type, + newAccountData.toJson(), + ); + } + accountData[newAccountData.type] = newAccountData; + if (onAccountData != null) onAccountData.add(newAccountData); + } } - if (sync['device_lists'] is Map) { - await _handleDeviceListsEvents(sync['device_lists']); + if (sync.deviceLists != null) { + await _handleDeviceListsEvents(sync.deviceLists); } - if (sync['device_one_time_keys_count'] is Map) { - _handleDeviceOneTimeKeysCount(sync['device_one_time_keys_count']); + if (sync.deviceOneTimeKeysCount != null) { + _handleDeviceOneTimeKeysCount(sync.deviceOneTimeKeysCount); } while (_pendingToDeviceEvents.isNotEmpty) { _updateRoomsByToDeviceEvent( @@ -1086,13 +796,12 @@ class Client { onSync.add(sync); } - void _handleDeviceOneTimeKeysCount( - Map deviceOneTimeKeysCount) { + void _handleDeviceOneTimeKeysCount(Map deviceOneTimeKeysCount) { if (!encryptionEnabled) 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 (deviceOneTimeKeysCount['signed_curve25519'] is int) { - final int oneTimeKeysCount = deviceOneTimeKeysCount['signed_curve25519']; + if (deviceOneTimeKeysCount['signed_curve25519'] != null) { + final oneTimeKeysCount = deviceOneTimeKeysCount['signed_curve25519']; if (oneTimeKeysCount < (_olmAccount.max_number_of_one_time_keys() / 2)) { // Generate and upload more one time keys: _uploadKeys(); @@ -1100,10 +809,9 @@ class Client { } } - Future _handleDeviceListsEvents( - Map deviceLists) async { - if (deviceLists['changed'] is List) { - for (final userId in deviceLists['changed']) { + Future _handleDeviceListsEvents(DeviceListsUpdate deviceLists) async { + if (deviceLists.changed is List) { + for (final userId in deviceLists.changed) { if (_userDeviceKeys.containsKey(userId)) { _userDeviceKeys[userId].outdated = true; if (database != null) { @@ -1111,7 +819,7 @@ class Client { } } } - for (final userId in deviceLists['left']) { + for (final userId in deviceLists.left) { if (_userDeviceKeys.containsKey(userId)) { _userDeviceKeys.remove(userId); } @@ -1143,18 +851,10 @@ class Client { _keyVerificationRequests[request.transactionId] = request; } - void _handleToDeviceEvents(List events) { + void _handleToDeviceEvents(List events) { for (var i = 0; i < events.length; i++) { - var isValid = events[i] is Map && - events[i]['type'] is String && - events[i]['sender'] is String && - events[i]['content'] is Map; - if (!isValid) { - print('[Sync] Invalid To Device Event! ${events[i]}'); - continue; - } - var toDeviceEvent = ToDeviceEvent.fromJson(events[i]); - if (toDeviceEvent.type == 'm.room.encrypted') { + var toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson()); + if (toDeviceEvent.type == EventTypes.Encrypted) { try { toDeviceEvent = decryptToDeviceEvent(toDeviceEvent); } catch (e, s) { @@ -1169,7 +869,7 @@ class Client { toDeviceEvent: toDeviceEvent, ), ); - toDeviceEvent = ToDeviceEvent.fromJson(events[i]); + toDeviceEvent = ToDeviceEvent.fromJson(events[i].toJson()); } } _updateRoomsByToDeviceEvent(toDeviceEvent); @@ -1215,56 +915,18 @@ class Client { } Future _handleRooms( - Map rooms, Membership membership) async { + Map rooms, Membership membership) async { for (final entry in rooms.entries) { final id = entry.key; final room = entry.value; - // calculate the notification counts, the limitedTimeline and prevbatch - num highlight_count = 0; - num notification_count = 0; - var prev_batch = ''; - var limitedTimeline = false; - if (room['unread_notifications'] is Map) { - if (room['unread_notifications']['highlight_count'] is num) { - highlight_count = room['unread_notifications']['highlight_count']; - } - if (room['unread_notifications']['notification_count'] is num) { - notification_count = - room['unread_notifications']['notification_count']; - } - } - - if (room['timeline'] is Map) { - if (room['timeline']['limited'] is bool) { - limitedTimeline = room['timeline']['limited']; - } - if (room['timeline']['prev_batch'] is String) { - prev_batch = room['timeline']['prev_batch']; - } - } - - RoomSummary summary; - - if (room['summary'] is Map) { - summary = RoomSummary.fromJson(room['summary']); - } - - var update = RoomUpdate( - id: id, - membership: membership, - notification_count: notification_count, - highlight_count: highlight_count, - limitedTimeline: limitedTimeline, - prev_batch: prev_batch, - summary: summary, - ); + var update = RoomUpdate.fromSyncRoomUpdate(room, id); if (database != null) { await database.storeRoomUpdate(this.id, update, getRoomById(id)); } _updateRoomsByRoomUpdate(update); final roomObj = getRoomById(id); - if (limitedTimeline && roomObj != null) { + if (update.limitedTimeline && roomObj != null) { roomObj.resetSortOrder(); } onRoomUpdate.add(update); @@ -1272,37 +934,47 @@ class Client { var handledEvents = false; /// Handle now all room events and save them in the database - if (room['state'] is Map && - room['state']['events'] is List && - room['state']['events'].isNotEmpty) { - await _handleRoomEvents(id, room['state']['events'], 'state'); - handledEvents = true; + if (room is JoinedRoomUpdate) { + if (room.state?.isNotEmpty ?? false) { + await _handleRoomEvents( + id, room.state.map((i) => i.toJson()).toList(), 'state'); + handledEvents = true; + } + if (room.timeline?.events?.isNotEmpty ?? false) { + await _handleRoomEvents(id, + room.timeline.events.map((i) => i.toJson()).toList(), 'timeline'); + handledEvents = true; + } + if (room.ephemeral?.isNotEmpty ?? false) { + await _handleEphemerals( + id, room.ephemeral.map((i) => i.toJson()).toList()); + } + if (room.accountData?.isNotEmpty ?? false) { + await _handleRoomEvents(id, + room.accountData.map((i) => i.toJson()).toList(), 'account_data'); + } } - - if (room['invite_state'] is Map && - room['invite_state']['events'] is List) { - await _handleRoomEvents( - id, room['invite_state']['events'], 'invite_state'); + if (room is LeftRoomUpdate) { + if (room.timeline?.events?.isNotEmpty ?? false) { + await _handleRoomEvents(id, + room.timeline.events.map((i) => i.toJson()).toList(), 'timeline'); + handledEvents = true; + } + if (room.accountData?.isNotEmpty ?? false) { + await _handleRoomEvents(id, + room.accountData.map((i) => i.toJson()).toList(), 'account_data'); + } + if (room.state?.isNotEmpty ?? false) { + await _handleRoomEvents( + id, room.state.map((i) => i.toJson()).toList(), 'state'); + handledEvents = true; + } } - - if (room['timeline'] is Map && - room['timeline']['events'] is List && - room['timeline']['events'].isNotEmpty) { - await _handleRoomEvents(id, room['timeline']['events'], 'timeline'); - handledEvents = true; + if (room is InvitedRoomUpdate && + (room.inviteState?.isNotEmpty ?? false)) { + await _handleRoomEvents(id, + room.inviteState.map((i) => i.toJson()).toList(), 'invite_state'); } - - if (room['ephemeral'] is Map && - room['ephemeral']['events'] is List) { - await _handleEphemerals(id, room['ephemeral']['events']); - } - - if (room['account_data'] is Map && - room['account_data']['events'] is List) { - await _handleRoomEvents( - id, room['account_data']['events'], 'account_data'); - } - if (handledEvents && database != null && roomObj != null) { await roomObj.updateSortOrder(); } @@ -1360,23 +1032,6 @@ class Client { } } - Future _handleGlobalEvents(List events, String type) async { - for (var i = 0; i < events.length; i++) { - if (events[i]['type'] is String && - events[i]['content'] is Map) { - var update = UserUpdate( - eventType: events[i]['type'], - type: type, - content: events[i], - ); - if (database != null) { - await database.storeUserEventUpdate(id, update); - } - onUserEvent.add(update); - } - } - } - Future _handleEvent( Map event, String roomID, String type) async { if (event['type'] is String && event['content'] is Map) { @@ -1384,10 +1039,10 @@ class Client { // man-in-the-middle attacks! final room = getRoomById(roomID); if (room == null || - (event['type'] == 'm.room.encryption' && + (event['type'] == EventTypes.Encryption && room.encrypted && event['content']['algorithm'] != - room.getState('m.room.encryption')?.content['algorithm'])) { + room.getState(EventTypes.Encryption)?.content['algorithm'])) { return; } @@ -1401,10 +1056,10 @@ class Client { content: event, sortOrder: sortOrder, ); - if (event['type'] == 'm.room.encrypted') { + if (event['type'] == EventTypes.Encrypted) { update = update.decrypt(room); } - if (update.eventType == 'm.room.encrypted' && database != null) { + if (update.eventType == EventTypes.Encrypted && database != null) { // the event is still encrytped....let's try fetching the keys from the database! await room.loadInboundGroupSessionKey( event['content']['session_id'], event['content']['sender_key']); @@ -1515,19 +1170,18 @@ class Client { ), ); } else { - var prevState = - rooms[j].getState(stateEvent.typeKey, stateEvent.stateKey); + var prevState = rooms[j].getState(stateEvent.type, stateEvent.stateKey); if (prevState != null && - prevState.time.millisecondsSinceEpoch > - stateEvent.time.millisecondsSinceEpoch) return; + prevState.originServerTs.millisecondsSinceEpoch > + stateEvent.originServerTs.millisecondsSinceEpoch) return; rooms[j].setState(stateEvent); } } else if (eventUpdate.type == 'account_data') { rooms[j].roomAccountData[eventUpdate.eventType] = - RoomAccountData.fromJson(eventUpdate.content, rooms[j]); + BasicRoomEvent.fromJson(eventUpdate.content); } else if (eventUpdate.type == 'ephemeral') { rooms[j].ephemerals[eventUpdate.eventType] = - RoomAccountData.fromJson(eventUpdate.content, rooms[j]); + BasicRoomEvent.fromJson(eventUpdate.content); } if (rooms[j].onUpdate != null) rooms[j].onUpdate.add(rooms[j].id); if (eventUpdate.type == 'timeline') _sortRooms(); @@ -1584,17 +1238,6 @@ class Client { _sortLock = false; } - /// Gets an OpenID token object that the requester may supply to another service to verify their identity in Matrix. - /// The generated token is only valid for exchanging for user information from the federation API for OpenID. - Future requestOpenIdCredentials() async { - final response = await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/user/$userID/openid/request_token', - data: {}, - ); - return OpenIdCredentials.fromJson(response); - } - /// A map of known device keys per user. Map get userDeviceKeys => _userDeviceKeys; Map _userDeviceKeys = {}; @@ -1639,23 +1282,22 @@ class Client { if (outdatedLists.isNotEmpty) { // Request the missing device key lists from the server. - final response = await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/keys/query', - data: {'timeout': 10000, 'device_keys': outdatedLists}); + final response = + await api.requestDeviceKeys(outdatedLists, timeout: 10000); - for (final rawDeviceKeyListEntry in response['device_keys'].entries) { - final String userId = rawDeviceKeyListEntry.key; + for (final rawDeviceKeyListEntry in response.deviceKeys.entries) { + final userId = rawDeviceKeyListEntry.key; final oldKeys = Map.from(_userDeviceKeys[userId].deviceKeys); _userDeviceKeys[userId].deviceKeys = {}; for (final rawDeviceKeyEntry in rawDeviceKeyListEntry.value.entries) { - final String deviceId = rawDeviceKeyEntry.key; + final deviceId = rawDeviceKeyEntry.key; // Set the new device key for this device if (!oldKeys.containsKey(deviceId)) { - final entry = DeviceKeys.fromJson(rawDeviceKeyEntry.value); + final entry = + DeviceKeys.fromMatrixDeviceKeys(rawDeviceKeyEntry.value); if (entry.isValid) { _userDeviceKeys[userId].deviceKeys[deviceId] = entry; if (deviceId == deviceID && @@ -1792,7 +1434,6 @@ class Client { ], 'keys': {}, }, - 'one_time_keys': signedOneTimeKeys, }; if (uploadDeviceKeys) { final Map keys = @@ -1806,13 +1447,13 @@ class Client { } _olmAccount.mark_keys_as_published(); - final response = await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/keys/upload', - data: keysContent, + final response = await api.uploadDeviceKeys( + deviceKeys: uploadDeviceKeys + ? MatrixDeviceKeys.fromJson(keysContent['device_keys']) + : null, + oneTimeKeys: signedOneTimeKeys, ); - if (response['one_time_key_counts']['signed_curve25519'] != - oneTimeKeysCount) { + if (response['signed_curve25519'] != oneTimeKeysCount) { return false; } await database?.updateClientKeys(pickledOlmAccount, id); @@ -1822,7 +1463,7 @@ class Client { /// Try to decrypt a ToDeviceEvent encrypted with olm. ToDeviceEvent decryptToDeviceEvent(ToDeviceEvent toDeviceEvent) { - if (toDeviceEvent.type != 'm.room.encrypted') { + if (toDeviceEvent.type != EventTypes.Encrypted) { print( '[LibOlm] Warning! Tried to decrypt a not-encrypted to-device-event'); return toDeviceEvent; @@ -1935,17 +1576,15 @@ class Client { var sendToDeviceMessage = message; // Send with send-to-device messaging - var data = { - 'messages': {}, - }; + var data = >>{}; if (deviceKeys.isEmpty) { if (toUsers == null) { - data['messages'][userID] = {}; - data['messages'][userID]['*'] = sendToDeviceMessage; + data[userID] = {}; + data[userID]['*'] = sendToDeviceMessage; } else { for (var user in toUsers) { - data['messages'][user.id] = {}; - data['messages'][user.id]['*'] = sendToDeviceMessage; + data[user.id] = {}; + data[user.id]['*'] = sendToDeviceMessage; } } } else { @@ -1960,8 +1599,8 @@ class Client { } for (var i = 0; i < deviceKeys.length; i++) { var device = deviceKeys[i]; - if (!data['messages'].containsKey(device.userId)) { - data['messages'][device.userId] = {}; + if (!data.containsKey(device.userId)) { + data[device.userId] = {}; } if (encrypted) { @@ -1992,16 +1631,12 @@ class Client { }; } - data['messages'][device.userId][device.deviceId] = sendToDeviceMessage; + data[device.userId][device.deviceId] = sendToDeviceMessage; } } - if (encrypted) type = 'm.room.encrypted'; + if (encrypted) type = EventTypes.Encrypted; final messageID = generateUniqueTransactionId(); - await jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/sendToDevice/$type/$messageID', - data: data, - ); + await api.sendToDevice(type, messageID, data); } Future startOutgoingOlmSessions(List deviceKeys, @@ -2014,16 +1649,13 @@ class Client { requestingKeysFrom[device.userId][device.deviceId] = 'signed_curve25519'; } - final response = await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/keys/claim', - data: {'timeout': 10000, 'one_time_keys': requestingKeysFrom}, - ); + final response = + await api.requestOneTimeKeys(requestingKeysFrom, timeout: 10000); - for (var userKeysEntry in response['one_time_keys'].entries) { - final String userId = userKeysEntry.key; + for (var userKeysEntry in response.oneTimeKeys.entries) { + final userId = userKeysEntry.key; for (var deviceKeysEntry in userKeysEntry.value.entries) { - final String deviceId = deviceKeysEntry.key; + final deviceId = deviceKeysEntry.key; final fingerprintKey = userDeviceKeys[userId].deviceKeys[deviceId].ed25519Key; final identityKey = @@ -2047,70 +1679,6 @@ class Client { } } - /// Gets information about all devices for the current user. - Future> requestUserDevices() async { - final response = - await jsonRequest(type: HTTPType.GET, action: '/client/r0/devices'); - var userDevices = []; - for (final rawDevice in response['devices']) { - userDevices.add( - UserDevice.fromJson(rawDevice, this), - ); - } - return userDevices; - } - - /// Gets information about all devices for the current user. - Future requestUserDevice(String deviceId) async { - final response = await jsonRequest( - type: HTTPType.GET, action: '/client/r0/devices/$deviceId'); - return UserDevice.fromJson(response, this); - } - - /// Deletes the given devices, and invalidates any access token associated with them. - Future deleteDevices(List deviceIds, - {Map auth}) async { - await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/delete_devices', - data: { - 'devices': deviceIds, - if (auth != null) 'auth': auth, - }, - ); - return; - } - - /// Lists the public rooms on the server, with optional filter. - Future requestPublicRooms({ - int limit, - String since, - String genericSearchTerm, - String server, - bool includeAllNetworks, - String thirdPartyInstanceId, - }) async { - var action = '/client/r0/publicRooms'; - if (server != null) { - action += '?server=$server'; - } - final response = await jsonRequest( - type: HTTPType.POST, - action: action, - data: { - if (limit != null) 'limit': 10, - if (since != null) 'since': since, - if (genericSearchTerm != null) - 'filter': {'generic_search_term': genericSearchTerm}, - if (includeAllNetworks != null) - 'include_all_networks': includeAllNetworks, - if (thirdPartyInstanceId != null) - 'third_party_instance_id': thirdPartyInstanceId, - }, - ); - return PublicRoomsResponse.fromJson(response, this); - } - /// Whether all push notifications are muted using the [.m.rule.master] /// rule of the push rules: https://matrix.org/docs/spec/client_server/r0.6.0#m-rule-master bool get allPushNotificationsMuted { @@ -2133,10 +1701,11 @@ class Client { } Future setMuteAllPushNotifications(bool muted) async { - await jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/pushrules/global/override/.m.rule.master/enabled', - data: {'enabled': muted}, + await api.enablePushRule( + 'global', + PushRuleKind.override, + '.m.rule.master', + muted, ); return; } @@ -2145,20 +1714,14 @@ class Client { Future changePassword(String newPassword, {String oldPassword, Map auth}) async { try { - await jsonRequest( - type: HTTPType.POST, - action: '/client/r0/account/password', - data: { - 'new_password': newPassword, - if (oldPassword != null) - 'auth': { - 'type': 'm.login.password', - 'user': userID, - 'password': oldPassword, - }, - if (auth != null) 'auth': auth, - }, - ); + if (oldPassword != null) { + auth = { + 'type': 'm.login.password', + 'user': userID, + 'password': oldPassword, + }; + } + await api.changePassword(newPassword, auth: auth); } on MatrixException catch (matrixException) { if (!matrixException.requireAdditionalAuthentication) { rethrow; diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart index 9d69346..080ab21 100644 --- a/lib/src/database/database.dart +++ b/lib/src/database/database.dart @@ -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( @@ -134,20 +137,31 @@ class Database extends _$Database { return roomList; } - Future> getAccountData(int clientId) async { - final newAccountData = {}; + Future> getAccountData(int clientId) async { + final newAccountData = {}; 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> getPresences(int clientId) async { - final newPresences = {}; + Future> getPresences(int clientId) async { + final newPresences = {}; 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; } @@ -158,7 +172,7 @@ class Database extends _$Database { Future 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); @@ -219,16 +233,17 @@ class Database extends _$Database { /// Stores an UserUpdate object in the database. Must be called inside of /// [transaction]. Future 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 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'])); } } @@ -247,7 +262,7 @@ class Database extends _$Database { stateKey = eventContent['state_key']; } - if (eventUpdate.eventType == 'm.room.redaction') { + if (eventUpdate.eventType == EventTypes.Redaction) { await redactMessage(clientId, eventUpdate); } diff --git a/lib/src/event.dart b/lib/src/event.dart index 1462bc7..ce48f99 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -1,24 +1,19 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:convert'; @@ -27,49 +22,24 @@ import 'package:famedlysdk/famedlysdk.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 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 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 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 +71,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 content, + String type, + String eventId, + String roomId, + String senderId, + DateTime originServerTs, + Map unsigned, + Map 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 getMapFromPayload(dynamic payload) { if (payload is String) { @@ -125,6 +105,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 jsonPayload, Room room, [double sortOrder]) { @@ -136,11 +137,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 +163,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 toJson() { final data = {}; if (stateKey != null) data['state_key'] = stateKey; @@ -180,116 +182,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 +260,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 +290,7 @@ class Event { room.client.onEvent.add(EventUpdate( roomID: room.id, type: 'timeline', - eventType: typeKey, + eventType: type, content: { 'event_id': eventId, 'status': -2, @@ -434,7 +350,7 @@ class Event { await room.client.database?.storeEventUpdate( room.client.id, EventUpdate( - eventType: newEvent.typeKey, + eventType: newEvent.type, content: newEvent.toJson(), roomID: newEvent.roomId, type: updateType, @@ -473,7 +389,7 @@ class Event { Future 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 +657,7 @@ class Event { } break; default: - localizedBody = i18n.unknownEvent(typeKey); + localizedBody = i18n.unknownEvent(type); } // Hide reply fallback @@ -762,7 +678,7 @@ class Event { return localizedBody; } - static const Set textOnlyMessageTypes = { + static const Set textOnlyMessageTypes = { MessageTypes.Text, MessageTypes.Reply, MessageTypes.Notice, @@ -770,43 +686,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, -} diff --git a/lib/src/presence.dart b/lib/src/presence.dart deleted file mode 100644 index e351259..0000000 --- a/lib/src/presence.dart +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -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 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'], - ); - } -} diff --git a/lib/src/room.dart b/lib/src/room.dart index b629d51..331d773 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1,37 +1,31 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:async'; import 'dart:convert'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:pedantic/pedantic.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/event.dart'; -import 'package:famedlysdk/src/room_account_data.dart'; -import 'package:famedlysdk/src/sync/event_update.dart'; -import 'package:famedlysdk/src/sync/room_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:famedlysdk/src/utils/session_key.dart'; import 'package:image/image.dart'; @@ -82,10 +76,10 @@ class Room { StatesMap states = StatesMap(); /// Key-Value store for ephemerals. - Map ephemerals = {}; + Map ephemerals = {}; /// Key-Value store for private account data only visible for this user. - Map roomAccountData = {}; + Map roomAccountData = {}; olm.OutboundGroupSession get outboundGroupSession => _outboundGroupSession; olm.OutboundGroupSession _outboundGroupSession; @@ -154,10 +148,11 @@ class Room { _outboundGroupSessionCreationTime = DateTime.now(); _outboundGroupSessionSentMessages = 0; await _storeOutboundGroupSession(); - } catch (e) { + } catch (e, s) { print( '[LibOlm] Unable to send the session key to the participating devices: ' + e.toString()); + print(s); await clearOutboundGroupSession(); } return; @@ -192,7 +187,7 @@ class Room { wipe = true; } // next check if it needs to be rotated - final encryptionContent = getState('m.room.encryption')?.content; + final encryptionContent = getState(EventTypes.Encryption)?.content; final maxMessages = encryptionContent != null && encryptionContent['rotation_period_msgs'] is int ? encryptionContent['rotation_period_msgs'] @@ -272,8 +267,8 @@ class Room { } Future _tryAgainDecryptLastMessage() async { - if (getState('m.room.encrypted') != null) { - await getState('m.room.encrypted').decryptAndStore(); + if (getState(EventTypes.Encrypted) != null) { + await getState(EventTypes.Encrypted).decryptAndStore(); } } @@ -298,19 +293,19 @@ class Room { outboundGroupSession != null && state.type == EventTypes.RoomMember) { var newUser = state.asUser; - var oldUser = getState('m.room.member', newUser.id)?.asUser; + var oldUser = getState(EventTypes.RoomMember, newUser.id)?.asUser; if (oldUser == null || oldUser.membership != newUser.membership) { clearOutboundGroupSession(); } } - if ((getState(state.typeKey)?.time?.millisecondsSinceEpoch ?? 0) > - (state.time?.millisecondsSinceEpoch ?? 1)) { + if ((getState(state.type)?.originServerTs?.millisecondsSinceEpoch ?? 0) > + (state.originServerTs?.millisecondsSinceEpoch ?? 1)) { return; } - if (!states.states.containsKey(state.typeKey)) { - states.states[state.typeKey] = {}; + if (!states.states.containsKey(state.type)) { + states.states[state.type] = {}; } - states.states[state.typeKey][state.stateKey ?? ''] = state; + states.states[state.type][state.stateKey ?? ''] = state; } /// ID of the fully read marker event. @@ -328,8 +323,8 @@ class Room { StreamController.broadcast(); /// The name of the room if set by a participant. - String get name => states['m.room.name'] != null - ? states['m.room.name'].content['name'] + String get name => states[EventTypes.RoomName] != null + ? states[EventTypes.RoomName].content['name'] : ''; /// Returns a localized displayname for this server. If the room is a groupchat @@ -351,29 +346,29 @@ class Room { } /// The topic of the room if set by a participant. - String get topic => states['m.room.topic'] != null - ? states['m.room.topic'].content['topic'] + String get topic => states[EventTypes.RoomTopic] != null + ? states[EventTypes.RoomTopic].content['topic'] : ''; /// The avatar of the room if set by a participant. Uri get avatar { - if (states['m.room.avatar'] != null && - states['m.room.avatar'].content['url'] != null) { - return Uri.parse(states['m.room.avatar'].content['url']); + if (states[EventTypes.RoomAvatar] != null && + states[EventTypes.RoomAvatar].content['url'] != null) { + return Uri.parse(states[EventTypes.RoomAvatar].content['url']); } if (mHeroes != null && mHeroes.length == 1 && states[mHeroes[0]] != null) { return states[mHeroes[0]].asUser.avatarUrl; } if (membership == Membership.invite && - getState('m.room.member', client.userID) != null) { - return getState('m.room.member', client.userID).sender.avatarUrl; + getState(EventTypes.RoomMember, client.userID) != null) { + return getState(EventTypes.RoomMember, client.userID).sender.avatarUrl; } return null; } /// The address in the format: #roomname:homeserver.org. - String get canonicalAlias => states['m.room.canonical_alias'] != null - ? states['m.room.canonical_alias'].content['alias'] + String get canonicalAlias => states[EventTypes.RoomCanonicalAlias] != null + ? states[EventTypes.RoomCanonicalAlias].content['alias'] : ''; /// If this room is a direct chat, this is the matrix ID of the user. @@ -409,15 +404,15 @@ class Room { // perfect, it is only used for the room preview in the room list and sorting // said room list, so it should be good enough. var lastTime = DateTime.fromMillisecondsSinceEpoch(0); - var lastEvent = getState('m.room.message'); + var lastEvent = getState(EventTypes.Message); if (lastEvent == null) { states.forEach((final String key, final entry) { if (!entry.containsKey('')) return; final Event state = entry['']; - if (state.time != null && - state.time.millisecondsSinceEpoch > + if (state.originServerTs != null && + state.originServerTs.millisecondsSinceEpoch > lastTime.millisecondsSinceEpoch) { - lastTime = state.time; + lastTime = state.originServerTs; lastEvent = state; } }); @@ -475,8 +470,8 @@ class Room { mHeroes.any((h) => h.isNotEmpty)) { heroes = mHeroes; } else { - if (states['m.room.member'] is Map) { - for (var entry in states['m.room.member'].entries) { + if (states[EventTypes.RoomMember] is Map) { + for (var entry in states[EventTypes.RoomMember].entries) { Event state = entry.value; if (state.type == EventTypes.RoomMember && state.stateKey != client?.userID) heroes.add(state.stateKey); @@ -492,8 +487,10 @@ class Room { return displayname.substring(0, displayname.length - 2); } if (membership == Membership.invite && - getState('m.room.member', client.userID) != null) { - return getState('m.room.member', client.userID).sender.calcDisplayname(); + getState(EventTypes.RoomMember, client.userID) != null) { + return getState(EventTypes.RoomMember, client.userID) + .sender + .calcDisplayname(); } return 'Empty chat'; } @@ -510,29 +507,25 @@ class Room { /// When the last message received. DateTime get timeCreated { if (lastEvent != null) { - return lastEvent.time; + return lastEvent.originServerTs; } return DateTime.now(); } /// 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) async { - final resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/${id}/state/m.room.name', - data: {'name': newName}); - return resp['event_id']; - } + Future setName(String newName) => client.api.sendState( + id, + EventTypes.RoomName, + {'name': newName}, + ); /// Call the Matrix API to change the topic of this room. - Future setDescription(String newName) async { - final resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/${id}/state/m.room.topic', - data: {'topic': newName}); - return resp['event_id']; - } + Future setDescription(String newName) => client.api.sendState( + id, + EventTypes.RoomTopic, + {'topic': newName}, + ); /// return all current emote packs for this room Map> get emotePacks { @@ -679,13 +672,15 @@ class Room { encryptedThumbnail = await thumbnail.encrypt(); } } - final uploadResp = await client.upload( - file, + final uploadResp = await client.api.upload( + file.bytes, + file.path, contentType: sendEncrypted ? 'application/octet-stream' : null, ); if (thumbnail != null) { - thumbnailUploadResp = await client.upload( - thumbnail, + thumbnailUploadResp = await client.api.upload( + thumbnail.bytes, + thumbnail.path, contentType: sendEncrypted ? 'application/octet-stream' : null, ); } @@ -802,7 +797,10 @@ class Room { } if (thumbnail != null && !(encrypted && client.encryptionEnabled)) { var thumbnailName = file.path.split('/').last; - final thumbnailUploadResp = await client.upload(thumbnail); + final thumbnailUploadResp = await client.api.upload( + thumbnail.bytes, + thumbnail.path, + ); info['thumbnail_url'] = thumbnailUploadResp; info['thumbnail_info'] = { 'size': thumbnail.size, @@ -829,9 +827,9 @@ class Room { /// event ID generated from the server. Future sendEvent(Map content, {String type, String txid, Event inReplyTo}) async { - type = type ?? 'm.room.message'; + type = type ?? EventTypes.Message; final sendType = - (encrypted && client.encryptionEnabled) ? 'm.room.encrypted' : type; + (encrypted && client.encryptionEnabled) ? EventTypes.Encrypted : type; // Create new transaction id String messageID; @@ -883,13 +881,15 @@ class Room { // Send the text and on success, store and display a *sent* event. try { - final response = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/${id}/send/$sendType/$messageID', - data: encrypted && client.encryptionEnabled - ? await encryptGroupMessagePayload(content, type: type) - : content); - final String res = response['event_id']; + final sendMessageContent = encrypted && client.encryptionEnabled + ? await encryptGroupMessagePayload(content, type: type) + : content; + final res = await client.api.sendMessage( + id, + sendType, + messageID, + sendMessageContent, + ); eventUpdate.content['status'] = 1; eventUpdate.content['unsigned'] = {'transaction_id': messageID}; eventUpdate.content['event_id'] = res; @@ -916,9 +916,8 @@ class Room { /// automatically be set. Future join() async { try { - await client.jsonRequest( - type: HTTPType.POST, action: '/client/r0/rooms/${id}/join'); - final invitation = getState('m.room.member', client.userID); + await client.api.joinRoom(id); + final invitation = getState(EventTypes.RoomMember, client.userID); if (invitation != null && invitation.content['is_direct'] is bool && invitation.content['is_direct']) { @@ -943,83 +942,61 @@ class Room { /// chat, this will be removed too. Future leave() async { if (directChatMatrixID != '') await removeFromDirectChat(); - await client.jsonRequest( - type: HTTPType.POST, action: '/client/r0/rooms/${id}/leave'); + await client.api.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.jsonRequest( - type: HTTPType.POST, action: '/client/r0/rooms/${id}/forget'); + await client.api.forgetRoom(id); return; } /// Call the Matrix API to kick a user from this room. - Future kick(String userID) async { - await client.jsonRequest( - type: HTTPType.POST, - action: '/client/r0/rooms/${id}/kick', - data: {'user_id': userID}); - return; - } + Future kick(String userID) => client.api.kickFromRoom(id, userID); /// Call the Matrix API to ban a user from this room. - Future ban(String userID) async { - await client.jsonRequest( - type: HTTPType.POST, - action: '/client/r0/rooms/${id}/ban', - data: {'user_id': userID}); - return; - } + Future ban(String userID) => client.api.banFromRoom(id, userID); /// Call the Matrix API to unban a banned user from this room. - Future unban(String userID) async { - await client.jsonRequest( - type: HTTPType.POST, - action: '/client/r0/rooms/${id}/unban', - data: {'user_id': userID}); - return; - } + Future unban(String userID) => client.api.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 /// power level event, there might something broken and this returns null. Future setPower(String userID, int power) async { - if (states['m.room.power_levels'] == null) return null; - var powerMap = {}..addAll(states['m.room.power_levels'].content); + if (states[EventTypes.RoomPowerLevels] == null) return null; + final powerMap = {} + ..addAll(states[EventTypes.RoomPowerLevels].content); if (powerMap['users'] == null) powerMap['users'] = {}; powerMap['users'][userID] = power; - final resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.power_levels', - data: powerMap); - return resp['event_id']; + return await client.api.sendState( + id, + EventTypes.RoomPowerLevels, + powerMap, + ); } /// Call the Matrix API to invite a user to this room. - Future invite(String userID) async { - await client.jsonRequest( - type: HTTPType.POST, - action: '/client/r0/rooms/${id}/invite', - data: {'user_id': userID}); - return; - } + Future invite(String userID) => client.api.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 dynamic resp = await client.jsonRequest( - type: HTTPType.GET, - action: - '/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Client.messagesFilters}'); + final resp = await client.api.requestMessages( + id, + prev_batch, + Direction.b, + limit: historyCount, + filter: Client.messagesFilters, + ); if (onHistoryReceived != null) onHistoryReceived(); - prev_batch = resp['end']; + prev_batch = resp.end; final dbActions = Function()>[]; if (client.database != null) { @@ -1027,17 +1004,15 @@ class Room { () => client.database.setRoomPrevBatch(prev_batch, client.id, id)); } - if (!(resp['chunk'] is List && - resp['chunk'].length > 0 && - resp['end'] is String)) return; + if (!((resp.chunk?.isNotEmpty ?? false) && resp.end != null)) return; - if (resp['state'] is List) { - for (final state in resp['state']) { + if (resp.state != null) { + for (final state in resp.state) { var eventUpdate = EventUpdate( type: 'state', roomID: id, - eventType: state['type'], - content: state, + eventType: state.type, + content: state.toJson(), sortOrder: oldSortOrder, ).decrypt(this); client.onEvent.add(eventUpdate); @@ -1048,13 +1023,12 @@ class Room { } } - List history = resp['chunk']; - for (final hist in history) { + for (final hist in resp.chunk) { var eventUpdate = EventUpdate( type: 'history', roomID: id, - eventType: hist['type'], - content: hist, + eventType: hist.type, + content: hist.toJson(), sortOrder: oldSortOrder, ).decrypt(this); client.onEvent.add(eventUpdate); @@ -1064,8 +1038,8 @@ class Room { } } if (client.database != null) { - dbActions.add( - () => client.database.setRoomPrevBatch(resp['end'], client.id, id)); + dbActions + .add(() => client.database.setRoomPrevBatch(resp.end, client.id, id)); } await client.database?.transaction(() async { for (final f in dbActions) { @@ -1077,7 +1051,7 @@ class Room { RoomUpdate( id: id, membership: membership, - prev_batch: resp['end'], + prev_batch: resp.end, notification_count: notificationCount, highlight_count: highlightCount, ), @@ -1097,10 +1071,12 @@ class Room { directChats[userID] = [id]; } - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/user/${client.userID}/account_data/m.direct', - data: directChats); + await client.api.setRoomAccountData( + client.userID, + id, + 'm.direct', + directChats, + ); return; } @@ -1114,10 +1090,12 @@ class Room { return; } // Nothing to do here - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/user/${client.userID}/account_data/m.direct', - data: directChats); + await client.api.setRoomAccountData( + client.userID, + id, + 'm.direct', + directChats, + ); return; } @@ -1125,13 +1103,11 @@ class Room { Future sendReadReceipt(String eventID) async { notificationCount = 0; await client.database?.resetNotificationCount(client.id, id); - await client.jsonRequest( - type: HTTPType.POST, - action: '/client/r0/rooms/$id/read_markers', - data: { - 'm.fully_read': eventID, - 'm.read': eventID, - }); + await client.api.sendReadMarker( + id, + eventID, + readReceiptLocationEventId: eventID, + ); return; } @@ -1175,7 +1151,7 @@ class Room { } } - var newRoomAccountData = {}; + var newRoomAccountData = {}; if (roomAccountData != null) { var rawRoomAccountData; if (roomAccountData is Future) { @@ -1184,8 +1160,13 @@ class Room { rawRoomAccountData = roomAccountData; } for (final singleAccountData in rawRoomAccountData) { - final newData = RoomAccountData.fromDb(singleAccountData, newRoom); - newRoomAccountData[newData.typeKey] = newData; + final content = Event.getMapFromPayload(singleAccountData.content); + final newData = BasicRoomEvent( + content: content, + type: singleAccountData.type, + roomId: singleAccountData.roomId, + ); + newRoomAccountData[newData.type] = newData; } } newRoom.roomAccountData = newRoomAccountData; @@ -1235,8 +1216,8 @@ class Room { /// case. List getParticipants() { var userList = []; - if (states['m.room.member'] is Map) { - for (var entry in states['m.room.member'].entries) { + if (states[EventTypes.RoomMember] is Map) { + for (var entry in states[EventTypes.RoomMember].entries) { Event state = entry.value; if (state.type == EventTypes.RoomMember) userList.add(state.asUser); } @@ -1248,20 +1229,12 @@ class Room { /// from the store is not complete if the client uses lazy loading. Future> requestParticipants() async { if (participantListComplete) return getParticipants(); - var participants = []; - - dynamic res = await client.jsonRequest( - type: HTTPType.GET, action: '/client/r0/rooms/${id}/members'); - - for (num i = 0; i < res['chunk'].length; i++) { - var newUser = Event.fromJson(res['chunk'][i], this).asUser; - if (![Membership.leave, Membership.ban].contains(newUser.membership)) { - participants.add(newUser); - setState(newUser); - } - } - - return participants; + final matrixEvents = await client.api.requestMembers(id); + final users = + matrixEvents.map((e) => Event.fromMatrixEvent(e, this).asUser).toList(); + users.removeWhere( + (u) => [Membership.leave, Membership.ban].contains(u.membership)); + return users; } /// Checks if the local participant list of joined and invited users is complete. @@ -1296,15 +1269,17 @@ class Room { /// Requests a missing [User] for this room. Important for clients using /// lazy loading. Future requestUser(String mxID, {bool ignoreErrors = false}) async { - if (getState('m.room.member', mxID) != null) { - return getState('m.room.member', mxID).asUser; + if (getState(EventTypes.RoomMember, mxID) != null) { + return getState(EventTypes.RoomMember, mxID).asUser; } if (mxID == null || !_requestingMatrixIds.add(mxID)) return null; Map resp; try { - resp = await client.jsonRequest( - type: HTTPType.GET, - action: '/client/r0/rooms/$id/state/m.room.member/$mxID'); + resp = await client.api.requestStateContent( + id, + EventTypes.RoomMember, + mxID, + ); } catch (exception) { _requestingMatrixIds.remove(mxID); if (!ignoreErrors) rethrow; @@ -1320,7 +1295,7 @@ class Room { await client.database?.transaction(() async { final content = { 'sender': mxID, - 'type': 'm.room.member', + 'type': EventTypes.RoomMember, 'content': resp, 'state_key': mxID, }; @@ -1330,7 +1305,7 @@ class Room { content: content, roomID: id, type: 'state', - eventType: 'm.room.member', + eventType: EventTypes.RoomMember, sortOrder: 0.0), ); }); @@ -1341,15 +1316,14 @@ class Room { /// Searches for the event on the server. Returns null if not found. Future getEventById(String eventID) async { - final dynamic resp = await client.jsonRequest( - type: HTTPType.GET, action: '/client/r0/rooms/$id/event/$eventID'); - return Event.fromJson(resp, this); + final matrixEvent = await client.api.requestEvent(id, eventID); + return Event.fromMatrixEvent(matrixEvent, this); } /// Returns the power level of the given user ID. int getPowerLevelByUserId(String userId) { var powerLevel = 0; - Event powerLevelState = states['m.room.power_levels']; + Event powerLevelState = states[EventTypes.RoomPowerLevels]; if (powerLevelState == null) return powerLevel; if (powerLevelState.content['users_default'] is int) { powerLevel = powerLevelState.content['users_default']; @@ -1366,7 +1340,7 @@ class Room { /// Returns the power levels from all users for this room or null if not given. Map get powerLevels { - Event powerLevelState = states['m.room.power_levels']; + Event powerLevelState = states[EventTypes.RoomPowerLevels]; if (powerLevelState.content['users'] is Map) { return powerLevelState.content['users']; } @@ -1376,18 +1350,21 @@ 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.upload(file); - final setAvatarResp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.avatar/', - data: {'url': uploadResp}); - return setAvatarResp['event_id']; + final uploadResp = await client.api.upload(file.bytes, file.path); + return await client.api.sendState( + id, + EventTypes.RoomAvatar, + {'url': uploadResp}, + ); } bool _hasPermissionFor(String action) { - if (getState('m.room.power_levels') == null || - getState('m.room.power_levels').content[action] == null) return true; - return ownPowerLevel >= getState('m.room.power_levels').content[action]; + if (getState(EventTypes.RoomPowerLevels) == null || + getState(EventTypes.RoomPowerLevels).content[action] == null) { + return true; + } + return ownPowerLevel >= + getState(EventTypes.RoomPowerLevels).content[action]; } /// The level required to ban a user. @@ -1408,18 +1385,19 @@ class Room { /// The default level required to send state events. Can be overridden by the events key. bool get canSendDefaultStates => _hasPermissionFor('state_default'); - bool get canChangePowerLevel => canSendEvent('m.room.power_levels'); + bool get canChangePowerLevel => canSendEvent(EventTypes.RoomPowerLevels); bool canSendEvent(String eventType) { - if (getState('m.room.power_levels') == null) return true; - if (getState('m.room.power_levels').content['events'] == null || - getState('m.room.power_levels').content['events'][eventType] == null) { - return eventType == 'm.room.message' + if (getState(EventTypes.RoomPowerLevels) == null) return true; + if (getState(EventTypes.RoomPowerLevels).content['events'] == null || + getState(EventTypes.RoomPowerLevels).content['events'][eventType] == + null) { + return eventType == EventTypes.Message ? canSendDefaultMessages : canSendDefaultStates; } return ownPowerLevel >= - getState('m.room.power_levels').content['events'][eventType]; + getState(EventTypes.RoomPowerLevels).content['events'][eventType]; } /// Returns the [PushRuleState] for this room, based on the m.push_rules stored in @@ -1463,69 +1441,57 @@ class Room { /// Sends a request to the homeserver to set the [PushRuleState] for this room. /// Returns ErrorResponse if something goes wrong. - Future setPushRuleState(PushRuleState newState) async { + Future setPushRuleState(PushRuleState newState) async { if (newState == pushRuleState) return null; dynamic resp; switch (newState) { // All push notifications should be sent to the user case PushRuleState.notify: if (pushRuleState == PushRuleState.dont_notify) { - resp = await client.jsonRequest( - type: HTTPType.DELETE, - action: '/client/r0/pushrules/global/override/$id', - data: {}); + await client.api.deletePushRule('global', PushRuleKind.override, id); } else if (pushRuleState == PushRuleState.mentions_only) { - resp = await client.jsonRequest( - type: HTTPType.DELETE, - action: '/client/r0/pushrules/global/room/$id', - data: {}); + await client.api.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) { - resp = await client.jsonRequest( - type: HTTPType.DELETE, - action: '/client/r0/pushrules/global/override/$id', - data: {}); - resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/pushrules/global/room/$id', - data: { - 'actions': ['dont_notify'] - }); + await client.api.deletePushRule('global', PushRuleKind.override, id); + await client.api.setPushRule( + 'global', + PushRuleKind.room, + id, + [PushRuleAction.dont_notify], + ); } else if (pushRuleState == PushRuleState.notify) { - resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/pushrules/global/room/$id', - data: { - 'actions': ['dont_notify'] - }); + await client.api.setPushRule( + 'global', + PushRuleKind.room, + id, + [PushRuleAction.dont_notify], + ); } break; // No push notification should be ever sent for this room. case PushRuleState.dont_notify: if (pushRuleState == PushRuleState.mentions_only) { - resp = await client.jsonRequest( - type: HTTPType.DELETE, - action: '/client/r0/pushrules/global/room/$id', - data: {}); + await client.api.deletePushRule('global', PushRuleKind.room, id); } - resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/pushrules/global/override/$id', - data: { - 'actions': ['dont_notify'], - 'conditions': [ - {'key': 'room_id', 'kind': 'event_match', 'pattern': id} - ] - }); + await client.api.setPushRule( + 'global', + PushRuleKind.override, + id, + [PushRuleAction.dont_notify], + conditions: [ + PushConditions('event_match', key: 'room_id', pattern: id) + ], + ); } return resp; } /// Redacts this event. Returns [ErrorResponse] on error. - Future redactEvent(String eventId, + Future redactEvent(String eventId, {String reason, String txid}) async { // Create new transaction id String messageID; @@ -1537,23 +1503,20 @@ class Room { } var data = {}; if (reason != null) data['reason'] = reason; - final dynamic resp = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/redact/$eventId/$messageID', - data: data); - return resp; + return await client.api.redact( + id, + eventId, + messageID, + reason: reason, + ); } - Future sendTypingInfo(bool isTyping, {int timeout}) { + Future sendTypingInfo(bool isTyping, {int timeout}) { var data = { 'typing': isTyping, }; if (timeout != null) data['timeout'] = timeout; - return client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/${id}/typing/${client.userID}', - data: data, - ); + return client.api.sendTypingNotification(client.userID, id, isTyping); } /// This is sent by the caller when they wish to establish a call. @@ -1566,17 +1529,18 @@ class Room { Future inviteToCall(String callId, int lifetime, String sdp, {String type = 'offer', int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - final response = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/send/m.call.invite/$txid', - data: { + + return await client.api.sendMessage( + id, + EventTypes.CallInvite, + txid, + { 'call_id': callId, 'lifetime': lifetime, 'offer': {'sdp': sdp, 'type': type}, 'version': version, }, ); - return response['event_id']; } /// This is sent by callers after sending an invite and by the callee after answering. @@ -1604,16 +1568,16 @@ class Room { String txid, }) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - final response = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/send/m.call.candidates/$txid', - data: { + return await client.api.sendMessage( + id, + EventTypes.CallCandidates, + txid, + { 'call_id': callId, 'candidates': candidates, 'version': version, }, ); - return response['event_id']; } /// This event is sent by the callee when they wish to answer the call. @@ -1624,16 +1588,16 @@ class Room { Future answerCall(String callId, String sdp, {String type = 'answer', int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - final response = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/send/m.call.answer/$txid', - data: { + return await client.api.sendMessage( + id, + EventTypes.CallAnswer, + txid, + { 'call_id': callId, 'answer': {'sdp': sdp, 'type': type}, 'version': version, }, ); - return response['event_id']; } /// This event is sent by the callee when they wish to answer the call. @@ -1642,21 +1606,21 @@ class Room { Future hangupCall(String callId, {int version = 0, String txid}) async { txid ??= 'txid${DateTime.now().millisecondsSinceEpoch}'; - final response = await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/send/m.call.hangup/$txid', - data: { + return await client.api.sendMessage( + id, + EventTypes.CallHangup, + txid, + { 'call_id': callId, 'version': version, }, ); - return response['event_id']; } /// Returns all aliases for this room. List get aliases { var aliases = []; - for (var aliasEvent in states.states['m.room.aliases'].values) { + for (var aliasEvent in states.states[EventTypes.RoomAliases].values) { if (aliasEvent.content['aliases'] is List) { aliases.addAll(aliasEvent.content['aliases']); } @@ -1668,20 +1632,20 @@ class Room { /// it can be invite meaning that a user who wishes to join the room must first receive an invite /// to the room from someone already inside of the room. Currently, knock and private are reserved /// keywords which are not implemented. - JoinRules get joinRules => getState('m.room.join_rules') != null + JoinRules get joinRules => getState(EventTypes.RoomJoinRules) != null ? JoinRules.values.firstWhere( (r) => r.toString().replaceAll('JoinRules.', '') == - getState('m.room.join_rules').content['join_rule'], + getState(EventTypes.RoomJoinRules).content['join_rule'], orElse: () => null) : null; /// Changes the join rules. You should check first if the user is able to change it. Future setJoinRules(JoinRules joinRules) async { - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.join_rules/', - data: { + await client.api.sendState( + id, + EventTypes.RoomJoinRules, + { 'join_rule': joinRules.toString().replaceAll('JoinRules.', ''), }, ); @@ -1689,24 +1653,24 @@ class Room { } /// Whether the user has the permission to change the join rules. - bool get canChangeJoinRules => canSendEvent('m.room.join_rules'); + bool get canChangeJoinRules => canSendEvent(EventTypes.RoomJoinRules); /// This event controls whether guest users are allowed to join rooms. If this event /// is absent, servers should act as if it is present and has the guest_access value "forbidden". - GuestAccess get guestAccess => getState('m.room.guest_access') != null + GuestAccess get guestAccess => getState(EventTypes.GuestAccess) != null ? GuestAccess.values.firstWhere( (r) => r.toString().replaceAll('GuestAccess.', '') == - getState('m.room.guest_access').content['guest_access'], + getState(EventTypes.GuestAccess).content['guest_access'], orElse: () => GuestAccess.forbidden) : GuestAccess.forbidden; /// Changes the guest access. You should check first if the user is able to change it. Future setGuestAccess(GuestAccess guestAccess) async { - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.guest_access/', - data: { + await client.api.sendState( + id, + EventTypes.GuestAccess, + { 'guest_access': guestAccess.toString().replaceAll('GuestAccess.', ''), }, ); @@ -1714,25 +1678,25 @@ class Room { } /// Whether the user has the permission to change the guest access. - bool get canChangeGuestAccess => canSendEvent('m.room.guest_access'); + bool get canChangeGuestAccess => canSendEvent(EventTypes.GuestAccess); /// This event controls whether a user can see the events that happened in a room from before they joined. HistoryVisibility get historyVisibility => - getState('m.room.history_visibility') != null + getState(EventTypes.HistoryVisibility) != null ? HistoryVisibility.values.firstWhere( (r) => r.toString().replaceAll('HistoryVisibility.', '') == - getState('m.room.history_visibility') + getState(EventTypes.HistoryVisibility) .content['history_visibility'], orElse: () => null) : null; /// Changes the history visibility. You should check first if the user is able to change it. Future setHistoryVisibility(HistoryVisibility historyVisibility) async { - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.history_visibility/', - data: { + await client.api.sendState( + id, + EventTypes.HistoryVisibility, + { 'history_visibility': historyVisibility.toString().replaceAll('HistoryVisibility.', ''), }, @@ -1742,12 +1706,12 @@ class Room { /// Whether the user has the permission to change the history visibility. bool get canChangeHistoryVisibility => - canSendEvent('m.room.history_visibility'); + canSendEvent(EventTypes.HistoryVisibility); /// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported. /// Returns null if there is no encryption algorithm. - String get encryptionAlgorithm => getState('m.room.encryption') != null - ? getState('m.room.encryption').content['algorithm'].toString() + String get encryptionAlgorithm => getState(EventTypes.Encryption) != null + ? getState(EventTypes.Encryption).content['algorithm'].toString() : null; /// Checks if this room is encrypted. @@ -1756,10 +1720,10 @@ class Room { Future enableEncryption({int algorithmIndex = 0}) async { if (encrypted) throw ('Encryption is already enabled!'); final algorithm = Client.supportedGroupEncryptionAlgorithms[algorithmIndex]; - await client.jsonRequest( - type: HTTPType.PUT, - action: '/client/r0/rooms/$id/state/m.room.encryption/', - data: { + await client.api.sendState( + id, + EventTypes.Encryption, + { 'algorithm': algorithm, }, ); @@ -1811,7 +1775,7 @@ class Room { /// payload. This will create a new outgoingGroupSession if necessary. Future> encryptGroupMessagePayload( Map payload, - {String type = 'm.room.message'}) async { + {String type = EventTypes.Message}) async { if (!encrypted || !client.encryptionEnabled) return payload; if (encryptionAlgorithm != 'm.megolm.v1.aes-sha2') { throw ('Unknown encryption algorithm'); @@ -1910,8 +1874,8 @@ class Room { final decryptResult = inboundGroupSessions[sessionId] .inboundGroupSession .decrypt(event.content['ciphertext']); - final messageIndexKey = - event.eventId + event.time.millisecondsSinceEpoch.toString(); + final messageIndexKey = event.eventId + + event.originServerTs.millisecondsSinceEpoch.toString(); if (inboundGroupSessions[sessionId] .indexes .containsKey(messageIndexKey) && @@ -1944,7 +1908,7 @@ class Room { if (exception.toString() == DecryptError.UNKNOWN_SESSION) { decryptedPayload = { 'content': event.content, - 'type': 'm.room.encrypted', + 'type': EventTypes.Encrypted, }; decryptedPayload['content']['body'] = exception.toString(); decryptedPayload['content']['msgtype'] = 'm.bad.encrypted'; @@ -1954,7 +1918,7 @@ class Room { 'msgtype': 'm.bad.encrypted', 'body': exception.toString(), }, - 'type': 'm.room.encrypted', + 'type': EventTypes.Encrypted, }; } } @@ -1964,12 +1928,12 @@ class Room { } return Event( content: decryptedPayload['content'], - typeKey: decryptedPayload['type'], + type: decryptedPayload['type'], senderId: event.senderId, eventId: event.eventId, roomId: event.roomId, room: event.room, - time: event.time, + originServerTs: event.originServerTs, unsigned: event.unsigned, stateKey: event.stateKey, prevContent: event.prevContent, diff --git a/lib/src/room_account_data.dart b/lib/src/room_account_data.dart deleted file mode 100644 index c82c6eb..0000000 --- a/lib/src/room_account_data.dart +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -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 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 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); - } -} diff --git a/lib/src/sync/room_update.dart b/lib/src/sync/room_update.dart deleted file mode 100644 index 69a65cb..0000000 --- a/lib/src/sync/room_update.dart +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -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 mHeroes; - int mJoinedMemberCount; - int mInvitedMemberCount; - - RoomSummary( - {this.mHeroes, this.mJoinedMemberCount, this.mInvitedMemberCount}); - - RoomSummary.fromJson(Map json) { - mHeroes = json['m.heroes']?.cast(); - mJoinedMemberCount = json['m.joined_member_count']; - mInvitedMemberCount = json['m.invited_member_count']; - } -} diff --git a/lib/src/sync/user_update.dart b/lib/src/sync/user_update.dart deleted file mode 100644 index f4d36a7..0000000 --- a/lib/src/sync/user_update.dart +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -/// 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}); -} diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index aabf279..b428665 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -1,32 +1,29 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:async'; +import 'package:famedlysdk/matrix_api.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); @@ -135,7 +132,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 +158,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); diff --git a/lib/src/user.dart b/lib/src/user.dart index f333e0f..7dd9840 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -1,32 +1,26 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ 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; diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 68eb8e3..29754b8 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:famedlysdk/matrix_api.dart'; + import '../client.dart'; import '../database/database.dart' show DbUserDeviceKey, DbUserDeviceKeysKey; import '../event.dart'; @@ -55,13 +57,7 @@ class DeviceKeysList { DeviceKeysList(this.userId); } -class DeviceKeys { - String userId; - String deviceId; - List algorithms; - Map keys; - Map signatures; - Map unsigned; +class DeviceKeys extends MatrixDeviceKeys { bool verified; bool blocked; @@ -93,63 +89,68 @@ class DeviceKeys { } DeviceKeys({ - this.userId, - this.deviceId, - this.algorithms, - this.keys, - this.signatures, - this.unsigned, + String userId, + String deviceId, + List algorithms, + Map keys, + Map> signatures, + Map unsigned, this.verified, this.blocked, - }); + }) : super(userId, deviceId, algorithms, keys, signatures, + unsigned: unsigned); - DeviceKeys.fromDb(DbUserDeviceKeysKey dbEntry) { + 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); - userId = dbEntry.userId; - deviceId = dbEntry.deviceId; - algorithms = content['algorithms'].cast(); - keys = content['keys'] != null + deviceKeys.userId = dbEntry.userId; + deviceKeys.deviceId = dbEntry.deviceId; + deviceKeys.algorithms = content['algorithms'].cast(); + deviceKeys.keys = content['keys'] != null ? Map.from(content['keys']) : null; - signatures = content['signatures'] != null - ? Map.from(content['signatures']) + deviceKeys.signatures = content['signatures'] != null + ? Map>.from((content['signatures'] as Map) + .map((k, v) => MapEntry(k, Map.from(v)))) : null; - unsigned = content['unsigned'] != null + deviceKeys.unsigned = content['unsigned'] != null ? Map.from(content['unsigned']) : null; - verified = dbEntry.verified; - blocked = dbEntry.blocked; + deviceKeys.verified = dbEntry.verified; + deviceKeys.blocked = dbEntry.blocked; + return deviceKeys; } - DeviceKeys.fromJson(Map json) { - userId = json['user_id']; - deviceId = json['device_id']; - algorithms = json['algorithms'].cast(); - keys = json['keys'] != null ? Map.from(json['keys']) : null; - signatures = json['signatures'] != null - ? Map.from(json['signatures']) - : null; - unsigned = json['unsigned'] != null - ? Map.from(json['unsigned']) - : null; - verified = json['verified'] ?? false; - blocked = json['blocked'] ?? false; + static DeviceKeys fromJson(Map json) { + var matrixDeviceKeys = MatrixDeviceKeys.fromJson(json); + var deviceKeys = DeviceKeys( + userId: matrixDeviceKeys.userId, + deviceId: matrixDeviceKeys.deviceId, + algorithms: matrixDeviceKeys.algorithms, + keys: matrixDeviceKeys.keys, + signatures: matrixDeviceKeys.signatures, + unsigned: matrixDeviceKeys.unsigned, + ); + deviceKeys.verified = json['verified'] ?? false; + deviceKeys.blocked = json['blocked'] ?? false; + return deviceKeys; } + @override Map toJson() { - final data = {}; - data['user_id'] = userId; - data['device_id'] = deviceId; - data['algorithms'] = algorithms; - if (keys != null) { - data['keys'] = keys; - } - if (signatures != null) { - data['signatures'] = signatures; - } - if (unsigned != null) { - data['unsigned'] = unsigned; - } + final data = super.toJson(); data['verified'] = verified; data['blocked'] = blocked; return data; diff --git a/lib/src/sync/event_update.dart b/lib/src/utils/event_update.dart similarity index 58% rename from lib/src/sync/event_update.dart rename to lib/src/utils/event_update.dart index 51ff868..758fa92 100644 --- a/lib/src/sync/event_update.dart +++ b/lib/src/utils/event_update.dart @@ -1,27 +1,23 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ 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. @@ -47,14 +43,14 @@ class EventUpdate { {this.eventType, this.roomID, this.type, this.content, this.sortOrder}); EventUpdate decrypt(Room room) { - if (eventType != 'm.room.encrypted') { + if (eventType != EventTypes.Encrypted) { return this; } try { var decrpytedEvent = room.decryptGroupMessage(Event.fromJson(content, room, sortOrder)); return EventUpdate( - eventType: decrpytedEvent.typeKey, + eventType: decrpytedEvent.type, roomID: roomID, type: type, content: decrpytedEvent.toJson(), diff --git a/lib/src/utils/key_verification.dart b/lib/src/utils/key_verification.dart index b6fdf64..b676a58 100644 --- a/lib/src/utils/key_verification.dart +++ b/lib/src/utils/key_verification.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:random_string/random_string.dart'; import 'package:canonical_json/canonical_json.dart'; import 'package:olm/olm.dart' as olm; +import '../../matrix_api.dart'; import 'device_keys_list.dart'; import '../client.dart'; import '../room.dart'; @@ -378,7 +379,7 @@ 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) { diff --git a/lib/src/utils/open_id_credentials.dart b/lib/src/utils/open_id_credentials.dart deleted file mode 100644 index 4a0852e..0000000 --- a/lib/src/utils/open_id_credentials.dart +++ /dev/null @@ -1,23 +0,0 @@ -class OpenIdCredentials { - String accessToken; - String tokenType; - String matrixServerName; - num expiresIn; - - OpenIdCredentials.fromJson(Map json) { - accessToken = json['access_token']; - tokenType = json['token_type']; - matrixServerName = json['matrix_server_name']; - expiresIn = json['expires_in']; - } - - Map toJson() { - var map = {}; - final data = map; - data['access_token'] = accessToken; - data['token_type'] = tokenType; - data['matrix_server_name'] = matrixServerName; - data['expires_in'] = expiresIn; - return data; - } -} diff --git a/lib/src/utils/profile.dart b/lib/src/utils/profile.dart deleted file mode 100644 index fcdec6a..0000000 --- a/lib/src/utils/profile.dart +++ /dev/null @@ -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 content; - - const Profile(this.displayname, this.avatarUrl, {this.content = const {}}); - - Profile.fromJson(Map 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; -} diff --git a/lib/src/utils/public_rooms_response.dart b/lib/src/utils/public_rooms_response.dart deleted file mode 100644 index 3f833be..0000000 --- a/lib/src/utils/public_rooms_response.dart +++ /dev/null @@ -1,48 +0,0 @@ -import '../client.dart'; - -class PublicRoomsResponse { - List publicRooms; - final String nextBatch; - final String prevBatch; - final int totalRoomCountEstimate; - Client client; - - PublicRoomsResponse.fromJson(Map json, Client client) - : nextBatch = json['next_batch'], - prevBatch = json['prev_batch'], - client = client, - totalRoomCountEstimate = json['total_room_count_estimate'] { - if (json['chunk'] != null) { - publicRooms = []; - json['chunk'].forEach((v) { - publicRooms.add(PublicRoomEntry.fromJson(v, client)); - }); - } - } -} - -class PublicRoomEntry { - final List 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 join() => client.joinRoomById(roomId); - - PublicRoomEntry.fromJson(Map json, Client client) - : aliases = - json.containsKey('aliases') ? json['aliases'].cast() : [], - 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; -} diff --git a/lib/src/utils/push_rules.dart b/lib/src/utils/push_rules.dart deleted file mode 100644 index 31f463a..0000000 --- a/lib/src/utils/push_rules.dart +++ /dev/null @@ -1,83 +0,0 @@ -/// The global ruleset. -class PushRules { - final GlobalPushRules global; - - PushRules.fromJson(Map json) - : global = GlobalPushRules.fromJson(json['global']); -} - -/// The global ruleset. -class GlobalPushRules { - final List content; - final List override; - final List room; - final List sender; - final List underride; - - GlobalPushRules.fromJson(Map 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 conditions; - final String pattern; - - static List fromJsonList(List list) { - var objList = []; - list.forEach((json) { - objList.add(PushRule.fromJson(json)); - }); - return objList; - } - - PushRule.fromJson(Map 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 fromJsonList(List list) { - var objList = []; - list.forEach((json) { - objList.add(PushRuleConditions.fromJson(json)); - }); - return objList; - } - - PushRuleConditions.fromJson(Map json) - : kind = json['kind'], - key = json['key'], - pattern = json['pattern'], - is_ = json['is']; -} diff --git a/lib/src/utils/pusher.dart b/lib/src/utils/pusher.dart deleted file mode 100644 index d4bd5da..0000000 --- a/lib/src/utils/pusher.dart +++ /dev/null @@ -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 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 toJson() { - final data = {}; - 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 json) { - url = json['url']; - format = json['format']; - } - - Map toJson() { - final data = {}; - if (url != null) data['url'] = url; - if (format != null) data['format'] = format; - return data; - } -} diff --git a/lib/src/utils/room_update.dart b/lib/src/utils/room_update.dart new file mode 100644 index 0000000..43f5e22 --- /dev/null +++ b/lib/src/utils/room_update.dart @@ -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 . + */ + +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; +} diff --git a/lib/src/utils/states_map.dart b/lib/src/utils/states_map.dart index b3b3a86..75fbcb8 100644 --- a/lib/src/utils/states_map.dart +++ b/lib/src/utils/states_map.dart @@ -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; diff --git a/lib/src/utils/to_device_event.dart b/lib/src/utils/to_device_event.dart index 7d1a2f7..729124a 100644 --- a/lib/src/utils/to_device_event.dart +++ b/lib/src/utils/to_device_event.dart @@ -1,28 +1,27 @@ -class ToDeviceEvent { - String sender; - String type; - Map content; +import 'package:famedlysdk/matrix_api.dart'; + +class ToDeviceEvent extends BasicEventWithSender { Map encryptedContent; - ToDeviceEvent({this.sender, this.type, this.content, this.encryptedContent}); + String get sender => senderId; + set sender(String sender) => senderId = sender; - ToDeviceEvent.fromJson(Map json) { - sender = json['sender']; - type = json['type']; - content = json['content'] != null - ? Map.from(json['content']) - : null; + ToDeviceEvent({ + String sender, + String type, + Map content, + this.encryptedContent, + }) { + senderId = sender; + this.type = type; + this.content = content; } - Map toJson() { - var map = {}; - final data = map; - data['sender'] = sender; - data['type'] = type; - if (content != null) { - data['content'] = content; - } - return data; + ToDeviceEvent.fromJson(Map json) { + 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, ); diff --git a/lib/src/utils/turn_server_credentials.dart b/lib/src/utils/turn_server_credentials.dart deleted file mode 100644 index 80cdb4a..0000000 --- a/lib/src/utils/turn_server_credentials.dart +++ /dev/null @@ -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 uris; - - /// The time-to-live in seconds - final double ttl; - - const TurnServerCredentials( - this.username, this.password, this.uris, this.ttl); - - TurnServerCredentials.fromJson(Map json) - : username = json['username'], - password = json['password'], - uris = json['uris'].cast(), - ttl = json['ttl']; -} diff --git a/lib/src/utils/uri_extension.dart b/lib/src/utils/uri_extension.dart index 4540600..edecdcf 100644 --- a/lib/src/utils/uri_extension.dart +++ b/lib/src/utils/uri_extension.dart @@ -1,24 +1,19 @@ /* - * Copyright (c) 2019 Zender & Kurtz GbR. + * Famedly Matrix SDK + * Copyright (C) 2019, 2020 Famedly GmbH * - * Authors: - * Christian Pauly - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ 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' : ''; } } diff --git a/lib/src/utils/user_device.dart b/lib/src/utils/user_device.dart deleted file mode 100644 index 190fe1c..0000000 --- a/lib/src/utils/user_device.dart +++ /dev/null @@ -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 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 deleteDevice(Map 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 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; -} diff --git a/lib/src/utils/well_known_informations.dart b/lib/src/utils/well_known_informations.dart deleted file mode 100644 index 359dbd0..0000000 --- a/lib/src/utils/well_known_informations.dart +++ /dev/null @@ -1,23 +0,0 @@ -class WellKnownInformations { - MHomeserver mHomeserver; - MHomeserver mIdentityServer; - Map content; - - WellKnownInformations.fromJson(Map 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 json) { - baseUrl = json['base_url']; - } -} diff --git a/test/canonical_json_test.dart b/test/canonical_json_test.dart index a0b10dc..1125c48 100644 --- a/test/canonical_json_test.dart +++ b/test/canonical_json_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:canonical_json/canonical_json.dart'; diff --git a/test/client_test.dart b/test/client_test.dart index 8d49f49..2232c15 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:async'; @@ -26,14 +21,10 @@ 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,7 +37,6 @@ void main() { Future> roomUpdateListFuture; Future> eventUpdateListFuture; - Future> userUpdateListFuture; Future> toDeviceUpdateListFuture; const pickledOlmAccount = @@ -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'), @@ -192,14 +170,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, 3); expect(matrix.userDeviceKeys['@alice:example.com'].outdated, false); expect(matrix.userDeviceKeys['@alice:example.com'].deviceKeys.length, 2); expect( @@ -207,7 +185,7 @@ void main() { .verified, false); - await matrix.handleSync({ + await matrix.handleSync(SyncUpdate.fromJson({ 'device_lists': { 'changed': [ '@alice:example.com', @@ -216,12 +194,12 @@ void main() { '@bob:example.com', ], } - }); + })); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.userDeviceKeys.length, 2); expect(matrix.userDeviceKeys['@alice:example.com'].outdated, true); - await matrix.handleSync({ + await matrix.handleSync(SyncUpdate.fromJson({ 'rooms': { 'join': { '!726s6s6q:example.com': { @@ -240,7 +218,7 @@ void main() { } } } - }); + })); await Future.delayed(Duration(milliseconds: 50)); expect( @@ -253,27 +231,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 +254,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 +327,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(); @@ -390,12 +339,10 @@ void main() { }); 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 +352,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 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,8 +382,6 @@ 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(), @@ -533,9 +437,9 @@ void main() { test('Track oneTimeKeys', () async { if (matrix.encryptionEnabled) { var last = matrix.lastTimeKeysUploaded ?? DateTime.now(); - await matrix.handleSync({ + await matrix.handleSync(SyncUpdate.fromJson({ 'device_one_time_keys_count': {'signed_curve25519': 49} - }); + })); await Future.delayed(Duration(milliseconds: 50)); expect( matrix.lastTimeKeysUploaded.millisecondsSinceEpoch > @@ -548,7 +452,7 @@ void main() { expect(matrix.rooms[1].outboundGroupSession == null, true); await matrix.rooms[1].createOutboundGroupSession(); expect(matrix.rooms[1].outboundGroupSession != null, true); - await matrix.handleSync({ + await matrix.handleSync(SyncUpdate.fromJson({ 'device_lists': { 'changed': [ '@alice:example.com', @@ -557,7 +461,7 @@ void main() { '@bob:example.com', ], } - }); + })); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.rooms[1].outboundGroupSession != null, true); } @@ -568,7 +472,7 @@ void main() { expect(matrix.rooms[1].outboundGroupSession == null, true); await matrix.rooms[1].createOutboundGroupSession(); expect(matrix.rooms[1].outboundGroupSession != null, true); - await matrix.handleSync({ + await matrix.handleSync(SyncUpdate.fromJson({ 'rooms': { 'join': { '!726s6s6q:example.com': { @@ -588,7 +492,7 @@ void main() { } } } - }); + })); await Future.delayed(Duration(milliseconds: 50)); expect(matrix.rooms[1].outboundGroupSession != null, true); } @@ -627,29 +531,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, @@ -667,17 +557,17 @@ void main() { 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) { @@ -692,5 +582,12 @@ void main() { await client1.logout(); await client2.logout(); }); + test('changePassword', () async { + await matrix.changePassword('1234', oldPassword: '123456'); + }); + + test('dispose', () async { + await matrix.dispose(closeDatabase: true); + }); }); } diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index 08b83e2..87d35d8 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:convert'; diff --git a/test/event_test.dart b/test/event_test.dart index e57ec7c..ea1539b 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1,33 +1,30 @@ /* - * 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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:convert'; import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/matrix_api.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 +49,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 +123,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,25 +163,38 @@ void main() { }); test('redact', () async { - final room = Room(id: '1234', client: Client('testclient', debug: true)); - final redactionEventJson = { - 'content': {'reason': 'Spamming'}, - 'event_id': '143273582443PhrSn:example.org', - 'origin_server_ts': 1432735824653, - 'redacts': id, - 'room_id': '1234', - 'sender': '@example:example.org', - 'type': 'm.room.redaction', - 'unsigned': {'age': 1234} - }; - var redactedBecause = Event.fromJson(redactionEventJson, room); - var event = Event.fromJson(jsonObj, 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); + final redactJsonObj = Map.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', + 'origin_server_ts': 1432735824653, + 'redacts': id, + 'room_id': '1234', + 'sender': '@example:example.org', + 'type': 'm.room.redaction', + 'unsigned': {'age': 1234} + }; + var redactedBecause = Event.fromJson(redactionEventJson, 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 +208,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'); @@ -212,8 +225,8 @@ void main() { }); 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 +240,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 +258,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': 'This is an example text message', + '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 this 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 example 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); + }); }); } diff --git a/test/fake_database.dart b/test/fake_database.dart index de35190..7d85757 100644 --- a/test/fake_database.dart +++ b/test/fake_database.dart @@ -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 . + */ + 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()); } diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index 1b5d552..410b736 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:convert'; @@ -34,15 +29,25 @@ class FakeMatrixApi extends MockClient { FakeMatrixApi() : super((request) async { // Collect data from Request - var action = - request.url.path.split('/_matrix')[1] + '?' + request.url.query; - if (action.endsWith('?')) action = action.replaceAll('?', ''); + var action = request.url.path; + if (request.url.path.contains('/_matrix')) { + action = request.url.path.split('/_matrix').last + + '?' + + request.url.query; + } + + if (action.endsWith('?')) { + action = action.substring(0, action.length - 1); + } + if (action.endsWith('/')) { + action = action.substring(0, action.length - 1); + } final method = request.method; final dynamic data = method == 'GET' ? request.url.queryParameters : request.body; - var res = {}; + dynamic res = {}; - //print("$method request to $action with Data: $data"); + //print('\$method request to $action with Data: $data'); // Sync requests with timeout if (data is Map && data['timeout'] is String) { @@ -61,7 +66,7 @@ class FakeMatrixApi extends MockClient { calledEndpoints[action].add(data); if (api.containsKey(method) && api[method].containsKey(action)) { res = api[method][action](data); - if (res.containsKey('errcode')) { + if (res is Map && res.containsKey('errcode')) { return Response(json.encode(res), 405); } } else if (method == 'PUT' && @@ -138,11 +143,187 @@ class FakeMatrixApi extends MockClient { 'origin_server_ts': 1432735824653, 'unsigned': {'age': 1234} } - ] + ], + 'state': [], }; static Map syncResponse = { 'next_batch': Random().nextDouble().toString(), + 'rooms': { + 'join': { + '!726s6s6q:example.com': { + 'summary': { + 'm.heroes': ['@alice:example.com', '@bob:example.com'], + 'm.joined_member_count': 2, + 'm.invited_member_count': 0 + }, + 'unread_notifications': { + 'highlight_count': 2, + 'notification_count': 2, + }, + 'state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@alice:example.com', + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid', + }, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.canonical_alias', + 'content': { + 'alias': '#famedlyContactDiscovery:fakeServer.notExisting' + }, + 'state_key': '', + 'origin_server_ts': 1417731086796, + 'event_id': '66697273743032:example.com' + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.encryption', + 'state_key': '', + 'content': {'algorithm': 'm.megolm.v1.aes-sha2'}, + 'origin_server_ts': 1417731086795, + 'event_id': '666972737430353:example.com' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'join'}, + 'prev_content': {'membership': 'invite'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r6432:example.com', + 'unsigned': {'foo': 'bar'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': {'body': 'I am a fish', 'msgtype': 'm.text'}, + 'origin_server_ts': 1417731086797, + 'event_id': '74686972643033:example.com' + } + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'ephemeral': { + 'events': [ + { + 'type': 'm.typing', + 'content': { + 'user_ids': ['@alice:example.com'] + } + }, + { + 'content': { + '7365636s6r6432:example.com': { + 'm.read': { + '@alice:example.com': {'ts': 1436451550453} + } + } + }, + 'room_id': '!726s6s6q:example.com', + 'type': 'm.receipt' + } + ] + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + 'invite': { + '!696r7674:example.com': { + 'invite_state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'My Room Name'} + }, + { + 'sender': '@alice:example.com', + 'type': 'm.room.member', + 'state_key': '@bob:example.com', + 'content': {'membership': 'invite'} + } + ] + } + } + }, + 'leave': { + '!726s6s6f:example.com': { + 'state': { + 'events': [ + { + 'sender': '@charley:example.com', + 'type': 'm.room.name', + 'state_key': '', + 'content': {'name': 'left room'}, + 'origin_server_ts': 1417731086795, + 'event_id': '66697273743031:example.com' + }, + ] + }, + 'timeline': { + 'events': [ + { + 'sender': '@bob:example.com', + 'type': 'm.room.message', + 'content': {'text': 'Hallo'}, + 'origin_server_ts': 1417731086795, + 'event_id': '7365636s6r64300:example.com', + 'unsigned': {'foo': 'bar'} + }, + ], + 'limited': true, + 'prev_batch': 't34-23535_0_0' + }, + 'account_data': { + 'events': [ + { + 'type': 'm.tag', + 'content': { + 'tags': { + 'work': {'order': 1} + } + } + }, + { + 'type': 'org.example.custom.room.config', + 'content': {'custom_config_key': 'custom_config_value'} + } + ] + } + } + }, + }, 'presence': { 'events': [ { @@ -359,126 +540,15 @@ class FakeMatrixApi extends MockClient { }, ] }, - 'rooms': { - 'join': { - '!726s6s6q:example.com': { - 'unread_notifications': { - 'highlight_count': 2, - 'notification_count': 2, - }, - 'state': { - 'events': [ - { - 'sender': '@alice:example.com', - 'type': 'm.room.member', - 'state_key': '@alice:example.com', - 'content': {'membership': 'join'}, - 'origin_server_ts': 1417731086795, - 'event_id': '66697273743031:example.com' - }, - { - 'sender': '@alice:example.com', - 'type': 'm.room.canonical_alias', - 'content': { - 'alias': '#famedlyContactDiscovery:fakeServer.notExisting' - }, - 'state_key': '', - 'origin_server_ts': 1417731086796, - 'event_id': '66697273743032:example.com' - }, - { - 'sender': '@alice:example.com', - 'type': 'm.room.encryption', - 'state_key': '', - 'content': {'algorithm': 'm.megolm.v1.aes-sha2'}, - 'origin_server_ts': 1417731086795, - 'event_id': '666972737430353:example.com' - }, - ] - }, - 'timeline': { - 'events': [ - { - 'sender': '@bob:example.com', - 'type': 'm.room.member', - 'state_key': '@bob:example.com', - 'content': {'membership': 'join'}, - 'prev_content': {'membership': 'invite'}, - 'origin_server_ts': 1417731086795, - 'event_id': '7365636s6r6432:example.com' - }, - { - 'sender': '@alice:example.com', - 'type': 'm.room.message', - 'txn_id': '1234', - 'content': {'body': 'I am a fish', 'msgtype': 'm.text'}, - 'origin_server_ts': 1417731086797, - 'event_id': '74686972643033:example.com' - } - ], - 'limited': true, - 'prev_batch': 't34-23535_0_0' - }, - 'ephemeral': { - 'events': [ - { - 'type': 'm.typing', - 'content': { - 'user_ids': ['@alice:example.com'] - } - }, - { - 'content': { - '7365636s6r6432:example.com': { - 'm.read': { - '@alice:example.com': {'ts': 1436451550453} - } - } - }, - 'room_id': '!726s6s6q:example.com', - 'type': 'm.receipt' - } - ] - }, - 'account_data': { - 'events': [ - { - 'type': 'm.tag', - 'content': { - 'tags': { - 'work': {'order': 1} - } - } - }, - { - 'type': 'org.example.custom.room.config', - 'content': {'custom_config_key': 'custom_config_value'} - } - ] - } - } - }, - 'invite': { - '!696r7674:example.com': { - 'invite_state': { - 'events': [ - { - 'sender': '@alice:example.com', - 'type': 'm.room.name', - 'state_key': '', - 'content': {'name': 'My Room Name'} - }, - { - 'sender': '@alice:example.com', - 'type': 'm.room.member', - 'state_key': '@bob:example.com', - 'content': {'membership': 'invite'} - } - ] - } - } - }, - } + 'device_lists': { + 'changed': [ + '@alice:example.com', + ], + 'left': [ + '@bob:example.com', + ], + }, + 'device_one_time_keys_count': {'curve25519': 10, 'signed_curve25519': 20}, }; static Map archiveSyncResponse = { @@ -555,6 +625,297 @@ class FakeMatrixApi extends MockClient { static final Map> api = { 'GET': { + '/path/to/auth/error': (var req) => { + 'errcode': 'M_FORBIDDEN', + 'error': 'Blabla', + }, + '/media/r0/preview_url?url=https%3A%2F%2Fmatrix.org&ts=10': (var req) => { + 'og:title': 'Matrix Blog Post', + 'og:description': 'This is a really cool blog post from matrix.org', + 'og:image': 'mxc://example.com/ascERGshawAWawugaAcauga', + 'og:image:type': 'image/png', + 'og:image:height': 48, + 'og:image:width': 48, + 'matrix:image:size': 102400 + }, + '/media/r0/config': (var req) => {'m.upload.size': 50000000}, + '/.well-known/matrix/client': (var req) => { + 'm.homeserver': {'base_url': 'https://matrix.example.com'}, + 'm.identity_server': {'base_url': 'https://identity.example.com'}, + 'org.example.custom.property': { + 'app_url': 'https://custom.app.example.org' + } + }, + '/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags': + (var req) => { + 'tags': { + 'm.favourite': {'order': 0.1}, + 'u.Work': {'order': 0.7}, + 'u.Customers': {} + } + }, + '/client/r0/events?from=1234&timeout=10&roomId=%211234': (var req) => { + 'start': 's3456_9_0', + 'end': 's3457_9_0', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!somewhere:over.the.rainbow', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ] + }, + '/client/r0/thirdparty/location?alias=1234': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/r0/thirdparty/location/irc': (var req) => [ + { + 'alias': '#freenode_#matrix:matrix.org', + 'protocol': 'irc', + 'fields': {'network': 'freenode', 'channel': '#matrix'} + } + ], + '/client/r0/thirdparty/user/irc': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/r0/thirdparty/user?userid=1234': (var req) => [ + { + 'userid': '@_gitter_jim:matrix.org', + 'protocol': 'gitter', + 'fields': {'user': 'jim'} + } + ], + '/client/r0/thirdparty/protocol/irc': (var req) => { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s#]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode'}, + 'network_id': 'freenode' + } + ] + }, + '/client/r0/thirdparty/protocols': (var req) => { + 'irc': { + 'user_fields': ['network', 'nickname'], + 'location_fields': ['network', 'channel'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'network': { + 'regexp': '([a-z0-9]+\\.)*[a-z0-9]+', + 'placeholder': 'irc.example.org' + }, + 'nickname': {'regexp': '[^\\s]+', 'placeholder': 'username'}, + 'channel': {'regexp': '#[^\\s]+', 'placeholder': '#foobar'} + }, + 'instances': [ + { + 'network_id': 'freenode', + 'desc': 'Freenode', + 'icon': 'mxc://example.org/JkLmNoPq', + 'fields': {'network': 'freenode.net'} + } + ] + }, + 'gitter': { + 'user_fields': ['username'], + 'location_fields': ['room'], + 'icon': 'mxc://example.org/aBcDeFgH', + 'field_types': { + 'username': {'regexp': '@[^\\s]+', 'placeholder': '@username'}, + 'room': { + 'regexp': '[^\\s]+\\/[^\\s]+', + 'placeholder': 'matrix-org/matrix-doc' + } + }, + 'instances': [ + { + 'network_id': 'gitter', + 'desc': 'Gitter', + 'icon': 'mxc://example.org/zXyWvUt', + 'fields': {} + } + ] + } + }, + '/client/r0/account/whoami': (var req) => + {'user_id': 'alice@example.com'}, + '/client/r0/capabilities': (var req) => { + 'capabilities': { + 'm.change_password': {'enabled': false}, + 'm.room_versions': { + 'default': '1', + 'available': { + '1': 'stable', + '2': 'stable', + '3': 'unstable', + 'test-version': 'unstable' + } + }, + 'com.example.custom.ratelimit': {'max_requests_per_hour': 600} + } + }, + '/client/r0/rooms/1234/context/1234?filter=%7B%7D&limit=10': (var req) => + { + 'end': 't29-57_2_0_2', + 'events_after': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'event': { + 'content': { + 'body': 'filename.jpg', + 'info': { + 'h': 398, + 'w': 394, + 'mimetype': 'image/jpeg', + 'size': 31037 + }, + 'url': 'mxc://example.org/JWEIFJgwEIhweiWJE', + 'msgtype': 'm.image' + }, + 'type': 'm.room.message', + 'event_id': '\$f3h4d129462ha:example.com', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + 'events_before': [ + { + 'content': { + 'body': 'something-important.doc', + 'filename': 'something-important.doc', + 'info': {'mimetype': 'application/msword', 'size': 46144}, + 'msgtype': 'm.file', + 'url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + ], + 'start': 't27-54_2_0_2', + 'state': [ + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + } + ] + }, + '/client/r0/admin/whois/%40alice%3Aexample.com': (var req) => { + 'user_id': '@peter:rabbit.rocks', + 'devices': { + 'teapot': { + 'sessions': [ + { + 'connections': [ + { + 'ip': '127.0.0.1', + 'last_seen': 1411996332123, + 'user_agent': 'curl/7.31.0-DEV' + }, + { + 'ip': '10.0.0.2', + 'last_seen': 1411996332123, + 'user_agent': + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36' + } + ] + } + ] + } + } + }, + '/client/r0/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/r0/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {'foo': 'bar'}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => { + 'room_id': '!abnjk1jdasj98:capuchins.com', + 'servers': ['capuchins.com', 'matrix.org', 'another.com'] + }, + '/client/r0/account/3pid': (var req) => { + 'threepids': [ + { + 'medium': 'email', + 'address': 'monkey@banana.island', + 'validated_at': 1535176800000, + 'added_at': 1535336848756 + } + ] + }, '/client/r0/devices': (var req) => { 'devices': [ { @@ -565,12 +926,191 @@ class FakeMatrixApi extends MockClient { } ] }, + '/client/r0/notifications?from=1234&limit=10&only=1234': (var req) => { + 'next_token': 'abcdef', + 'notifications': [ + { + 'actions': ['notify'], + 'profile_tag': 'hcbvkzxhcvb', + 'read': true, + 'room_id': '!abcdefg:example.com', + 'ts': 1475508881945, + 'event': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!jEsUZKDJdhlrceRyVU:example.org', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + }, + '/client/r0/devices/QBUAZIFURK': (var req) => { + 'device_id': 'QBUAZIFURK', + 'display_name': 'android', + 'last_seen_ip': '1.2.3.4', + 'last_seen_ts': 1474491775024 + }, + '/client/r0/profile/%40alice%3Aexample.com/displayname': (var reqI) => + {'displayname': 'Alice M'}, + '/client/r0/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => + {'avatar_url': 'mxc://test'}, + '/client/r0/profile/%40alice%3Aexample.com': (var reqI) => { + 'avatar_url': 'mxc://test', + 'displayname': 'Alice M', + }, + '/client/r0/voip/turnServer': (var req) => { + 'username': '1443779631:@user:example.com', + 'password': 'JlKfBy1QwLrO20385QyAtEyIv0=', + 'uris': [ + 'turn:turn.example.com:3478?transport=udp', + 'turn:10.20.30.40:3478?transport=tcp', + 'turns:10.20.30.40:443?transport=tcp' + ], + 'ttl': 86400 + }, + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => { + 'presence': 'unavailable', + 'last_active_ago': 420845, + 'status_msg': 'test', + 'currently_active': false + }, + '/client/r0/keys/changes?from=1234&to=1234': (var req) => { + 'changed': ['@alice:example.com', '@bob:example.org'], + 'left': ['@clara:example.com', '@doug:example.org'] + }, + '/client/r0/pushers': (var req) => { + 'pushers': [ + { + 'pushkey': 'Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=', + 'kind': 'http', + 'app_id': 'face.mcapp.appy.prod', + 'app_display_name': 'Appy McAppface', + 'device_display_name': 'Alices Phone', + 'profile_tag': 'xyz', + 'lang': 'en-US', + 'data': { + 'url': 'https://example.com/_matrix/push/v1/notify', + 'format': 'event_id_only', + } + } + ] + }, + '/client/r0/publicRooms?limit=10&since=1234&server=example.com': + (var req) => { + 'chunk': [ + { + 'aliases': ['#murrays:cheese.bar'], + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, + '/client/r0/room/!localpart%3Aexample.com/aliases': (var req) => { + 'aliases': [ + '#somewhere:example.com', + '#another:example.com', + '#hat_trick:example.com' + ] + }, + '/client/r0/joined_rooms': (var req) => { + 'joined_rooms': ['!foo:example.com'] + }, + '/client/r0/directory/list/room/!localpart%3Aexample.com': (var req) => + {'visibility': 'public'}, '/client/r0/rooms/1/state/m.room.member/@alice:example.com': (var req) => {'displayname': 'Alice'}, - '/client/r0/profile/@getme:example.com': (var req) => { + '/client/r0/profile/%40getme%3Aexample.com': (var req) => { 'avatar_url': 'mxc://test', 'displayname': 'You got me', }, + '/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.member/@getme%3Aexample.com': + (var req) => { + 'avatar_url': 'mxc://test', + 'displayname': 'You got me', + }, + '/client/r0/rooms/!localpart%3Aserver.abc/state': (var req) => [ + { + 'content': {'join_rule': 'public'}, + 'type': 'm.room.join_rules', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.org' + }, + { + 'content': { + 'creator': '@example:example.org', + 'room_version': '1', + 'm.federate': true, + 'predecessor': { + 'event_id': '\$something:example.org', + 'room_id': '!oldroom:example.org' + } + }, + 'type': 'm.room.create', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'ban': 50, + 'events': {'m.room.name': 100, 'm.room.power_levels': 100}, + 'events_default': 0, + 'invite': 50, + 'kick': 50, + 'redact': 50, + 'state_default': 50, + 'users': {'@example:localhost': 100}, + 'users_default': 0, + 'notifications': {'room': 20} + }, + 'type': 'm.room.power_levels', + 'event_id': '\$143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + } + ], '/client/r0/rooms/!localpart:server.abc/state/m.room.member/@getme:example.com': (var req) => { 'avatar_url': 'mxc://test', @@ -590,9 +1130,25 @@ class FakeMatrixApi extends MockClient { 'origin_server_ts': 1432735824653, 'unsigned': {'age': 1234} }, - '/client/r0/rooms/!localpart:server.abc/messages?from=&dir=b&limit=10&filter=%7B%22lazy_load_members%22:true%7D': + '/client/r0/rooms/!localpart%3Aserver.abc/event/1234': (var req) => { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '143273582443PhrSn:example.org', + 'room_id': '!localpart:server.abc', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=1234&dir=b&to=1234&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': (var req) => messagesResponse, - '/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100&filter=%7B%22lazy_load_members%22:true%7D': + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=&dir=b&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => messagesResponse, + '/client/r0/rooms/!1234%3Aexample.com/messages?from=1234&dir=b&limit=100&filter=%7B%22lazy_load_members%22%3Atrue%7D': (var req) => messagesResponse, '/client/versions': (var req) => { 'versions': [ @@ -610,6 +1166,33 @@ class FakeMatrixApi extends MockClient { {'type': 'm.login.password'} ] }, + '/client/r0/rooms/!localpart%3Aserver.abc/joined_members': (var req) => { + 'joined': { + '@bar:example.com': { + 'display_name': 'Bar', + 'avatar_url': 'mxc://riot.ovh/printErCATzZijQsSDWorRaK' + } + } + }, + '/client/r0/rooms/!localpart%3Aserver.abc/members?at=1234&membership=join¬_membership=leave': + (var req) => { + 'chunk': [ + { + 'content': { + 'membership': 'join', + 'avatar_url': 'mxc://example.org/SEsfnsuifSDFSSEF', + 'displayname': 'Alice Margatroid' + }, + 'type': 'm.room.member', + 'event_id': 'ยง143273582443PhrSn:example.org', + 'room_id': '!636q39766251:example.com', + 'sender': '@alice:example.com', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '@alice:example.com' + } + ] + }, '/client/r0/rooms/!696r7674:example.com/members': (var req) => { 'chunk': [ { @@ -646,7 +1229,7 @@ class FakeMatrixApi extends MockClient { } ] }, - '/client/r0/rooms/!localpart:server.abc/members': (var req) => { + '/client/r0/rooms/!localpart%3Aserver.abc/members': (var req) => { 'chunk': [ { 'content': { @@ -664,7 +1247,20 @@ class FakeMatrixApi extends MockClient { } ] }, - '/client/r0/pushrules/': (var req) => { + '/client/r0/pushrules/global/content/nocake': (var req) => { + 'actions': ['dont_notify'], + 'pattern': 'cake*lie', + 'rule_id': 'nocake', + 'enabled': true, + 'default': false + }, + '/client/r0/pushrules/global/content/nocake/enabled': (var req) => { + 'enabled': true, + }, + '/client/r0/pushrules/global/content/nocake/actions': (var req) => { + 'actions': ['notify'] + }, + '/client/r0/pushrules': (var req) => { 'global': { 'content': [ { @@ -809,14 +1405,155 @@ class FakeMatrixApi extends MockClient { ] } }, - '/client/r0/sync?filter=%7B%22room%22:%7B%22include_leave%22:true,%22timeline%22:%7B%22limit%22:10%7D%7D%7D&timeout=0': + '/client/r0/sync?filter=%7B%22room%22%3A%7B%22include_leave%22%3Atrue%2C%22timeline%22%3A%7B%22limit%22%3A10%7D%7D%7D&timeout=0': (var req) => archiveSyncResponse, - '/client/r0/sync?filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D': + '/client/r0/sync?filter=%7B%22room%22%3A%7B%22state%22%3A%7B%22lazy_load_members%22%3Atrue%7D%7D%7D': + (var req) => syncResponse, + '/client/r0/sync?filter=%7B%7D&since=1234&full_state=false&set_presence=unavailable&timeout=15': (var req) => syncResponse, '/client/r0/register/available?username=testuser': (var req) => {'available': true}, + '/client/r0/user/${Uri.encodeComponent('alice@example.com')}/filter/1234': + (var req) => { + 'room': { + 'state': { + 'types': ['m.room.*'], + 'not_rooms': ['!726s6s6q:example.com'] + }, + 'timeline': { + 'limit': 10, + 'types': ['m.room.message'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'ephemeral': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'account_data': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + } + }, + 'presence': { + 'types': ['m.presence'], + 'not_senders': ['@alice:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'] + }, }, 'POST': { + '/client/r0/delete_devices': (var req) => {}, + '/client/r0/account/3pid/add': (var req) => {}, + '/client/r0/account/3pid/bind': (var req) => {}, + '/client/r0/account/3pid/delete': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/account/3pid/unbind': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/account/password': (var req) => {}, + '/client/r0/rooms/1234/report/1234': (var req) => {}, + '/client/r0/search': (var req) => { + 'search_categories': { + 'room_events': { + 'groups': { + 'room_id': { + '!qPewotXpIctQySfjSy:localhost': { + 'order': 1, + 'next_batch': 'BdgFsdfHSf-dsFD', + 'results': ['\$144429830826TWwbB:localhost'] + } + } + }, + 'highlights': ['martians', 'men'], + 'next_batch': '5FdgFsd234dfgsdfFD', + 'count': 1224, + 'results': [ + { + 'rank': 0.00424866, + 'result': { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': + 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '\$144429830826TWwbB:localhost', + 'room_id': '!qPewotXpIctQySfjSy:localhost', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + } + } + ] + } + } + }, + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status': + (var req) => {}, + '/client/r0/account/deactivate': (var req) => + {'id_server_unbind_result': 'success'}, + '/client/r0/user_directory/search': (var req) => { + 'results': [ + { + 'user_id': '@foo:bar.com', + 'display_name': 'Foo', + 'avatar_url': 'mxc://bar.com/foo' + } + ], + 'limited': false + }, + '/client/r0/register/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/register/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/password/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/password/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/3pid/email/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/account/3pid/msisdn/requestToken': (var req) => { + 'sid': '123abc', + 'submit_url': 'https://example.org/path/to/submitToken' + }, + '/client/r0/rooms/!localpart%3Aexample.com/receipt/m.read/%241234%3Aexample.com': + (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/read_markers': (var req) => {}, + '/client/r0/user/${Uri.encodeComponent('alice@example.com')}/filter': + (var req) => {'filter_id': '1234'}, + '/client/r0/publicRooms?server=example.com': (var req) => { + 'chunk': [ + { + 'aliases': ['#murrays:cheese.bar'], + 'canonical_alias': '#murrays:cheese.bar', + 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', + 'guest_can_join': false, + 'name': 'CHEESE', + 'num_joined_members': 37, + 'room_id': '!ol19s:bleecker.street', + 'topic': 'Tasty tasty cheese', + 'world_readable': true + } + ], + 'next_batch': 'p190q', + 'prev_batch': 'p1902', + 'total_room_count_estimate': 115 + }, '/client/r0/keys/claim': (var req) => { 'failures': {}, 'one_time_keys': { @@ -835,6 +1572,17 @@ class FakeMatrixApi extends MockClient { } } }, + '/client/r0/rooms/!localpart%3Aexample.com/invite': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/leave': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/forget': (var req) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/kick': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/kick': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/ban': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/unban': (var req) => {}, + '/client/r0/rooms/!localpart%3Aexample.com/join': (var req) => + {'room_id': '!localpart:example.com'}, + '/client/r0/join/!localpart%3Aexample.com?server_name=example.com&server_name=example.abc': + (var req) => {'room_id': '!localpart:example.com'}, '/client/r0/keys/upload': (var req) => { 'one_time_key_counts': { 'curve25519': 10, @@ -864,7 +1612,7 @@ class FakeMatrixApi extends MockClient { 'dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2giMIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA' } }, - 'unsigned': {'device_display_name': "Alice's mobile phone"} + 'unsigned': {'device_display_name': 'Alices mobile phone'} }, 'OTHERDEVICE': { 'user_id': '@alice:example.com', @@ -878,15 +1626,26 @@ class FakeMatrixApi extends MockClient { 'ed25519:OTHERDEVICE': 'blah' }, 'signatures': {}, - } + }, } } }, - '/client/r0/register': (var req) => {'user_id': '@testuser:example.com'}, + '/client/r0/register': (var req) => { + 'user_id': '@testuser:example.com', + 'access_token': '1234', + 'device_id': 'ABCD', + }, '/client/r0/register?kind=user': (var req) => {'user_id': '@testuser:example.com'}, '/client/r0/register?kind=guest': (var req) => {'user_id': '@testuser:example.com'}, + '/client/r0/rooms/1234/upgrade': (var req) => {}, + '/client/r0/user/1234/openid/request_token': (var req) => { + 'access_token': 'SomeT0kenHere', + 'token_type': 'Bearer', + 'matrix_server_name': 'example.com', + 'expires_in': 3600 + }, '/client/r0/user/@test:fakeServer.notExisting/openid/request_token': (var req) => { 'access_token': 'SomeT0kenHere', @@ -897,7 +1656,11 @@ class FakeMatrixApi extends MockClient { '/client/r0/login': (var req) => { 'user_id': '@test:fakeServer.notExisting', 'access_token': 'abc123', - 'device_id': 'GHTYAJCE' + 'device_id': 'GHTYAJCE', + 'well_known': { + 'm.homeserver': {'base_url': 'https://example.org'}, + 'm.identity_server': {'base_url': 'https://id.example.org'} + } }, '/media/r0/upload?filename=file.jpeg': (var req) => {'content_uri': 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'}, @@ -908,42 +1671,95 @@ class FakeMatrixApi extends MockClient { '/client/r0/createRoom': (var reqI) => { 'room_id': '!1234:fakeServer.notExisting', }, - '/client/r0/rooms/!localpart:server.abc/read_markers': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/read_markers': (var reqI) => {}, '/client/r0/rooms/!localpart:server.abc/kick': (var reqI) => {}, - '/client/r0/rooms/!localpart:server.abc/ban': (var reqI) => {}, - '/client/r0/rooms/!localpart:server.abc/unban': (var reqI) => {}, - '/client/r0/rooms/!localpart:server.abc/invite': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/ban': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/unban': (var reqI) => {}, + '/client/r0/rooms/!localpart%3Aserver.abc/invite': (var reqI) => {}, }, 'PUT': { - '/client/r0/rooms/!localpart:server.abc/send/m.room.message/testtxid': + '/client/r0/pushrules/global/content/nocake/enabled': (var req) => {}, + '/client/r0/pushrules/global/content/nocake/actions': (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.history_visibility': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.join_rules': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.guest_access': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.invite/1234': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.answer/1234': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.candidates/1234': + (var req) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.hangup/1234': + (var req) => {}, + '/client/r0/rooms/%211234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234': + (var req) => {'event_id': '1234'}, + '/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) => + {}, + '/client/r0/pushrules/global/override/.m.rule.master/enabled': + (var req) => {}, + '/client/r0/pushrules/global/content/nocake?before=1&after=2': + (var req) => {}, + '/client/r0/devices/QBUAZIFURK': (var req) => {}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => {}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.message/testtxid': (var reqI) => { 'event_id': '42', }, - '/client/r0/rooms/!1234:example.com/send/m.room.message/1234': + '/client/r0/rooms/!localpart%3Aexample.com/typing/%40alice%3Aexample.com': + (var req) => {}, + '/client/r0/rooms/%211234%3Aexample.com/send/m.room.message/1234': (var reqI) => { 'event_id': '42', }, - '/client/r0/profile/@test:fakeServer.notExisting/avatar_url': + '/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags/testtag': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/account_data/test.account.data': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data': + (var req) => {}, + '/client/r0/profile/%40alice%3Aexample.com/displayname': (var reqI) => {}, + '/client/r0/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => {}, + '/client/r0/profile/%40test%3AfakeServer.notExisting/avatar_url': (var reqI) => {}, - '/client/r0/rooms/!localpart:server.abc/state/m.room.avatar/': + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.encryption': (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, - '/client/r0/rooms/!localpart:server.abc/state/m.room.name': (var reqI) => - { - 'event_id': '42', - }, - '/client/r0/rooms/!localpart:server.abc/state/m.room.topic': (var reqI) => - { - 'event_id': '42', - }, - '/client/r0/rooms/!localpart:server.abc/state/m.room.power_levels': + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.avatar': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.message/1234': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/%21localpart%3Aserver.abc/redact/1234/1234': + (var reqI) => {'event_id': 'YUwRidLecu:example.com'}, + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.name': (var reqI) => { 'event_id': '42', }, - '/client/r0/user/@test:fakeServer.notExisting/account_data/m.direct': + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.topic': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.power_levels': + (var reqI) => { + 'event_id': '42', + }, + '/client/r0/user/%40test%3AfakeServer.notExisting/rooms/%211234%3AfakeServer.notExisting/account_data/m.direct': (var reqI) => {}, + '/client/r0/user/%40test%3AfakeServer.notExisting/rooms/%21localpart%3Aserver.abc/account_data/m.direct': + (var reqI) => {}, + '/client/r0/directory/list/room/!localpart%3Aexample.com': (var req) => + {}, }, 'DELETE': { '/unknown/token': (var req) => {'errcode': 'M_UNKNOWN_TOKEN'}, + '/client/r0/devices/QBUAZIFURK': (var req) => {}, + '/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => {}, + '/client/r0/pushrules/global/content/nocake': (var req) => {}, + '/client/r0/pushrules/global/override/!localpart%3Aserver.abc': + (var req) => {}, + '/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags/testtag': + (var req) => {}, }, }; } diff --git a/test/fake_matrix_localizations.dart b/test/fake_matrix_localizations.dart new file mode 100644 index 0000000..c5e7046 --- /dev/null +++ b/test/fake_matrix_localizations.dart @@ -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 . + */ + +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; +} diff --git a/test/key_verification_test.dart b/test/key_verification_test.dart new file mode 100644 index 0000000..0594bd2 --- /dev/null +++ b/test/key_verification_test.dart @@ -0,0 +1,97 @@ +/* + * 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 . + */ + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:test/test.dart'; +import 'package:olm/olm.dart' as olm; + +import 'fake_matrix_api.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'); + + var client = Client('testclient', debug: true, httpClient: FakeMatrixApi()); + client.api.homeserver = Uri.parse('https://fakeserver.notexisting'); + var room = Room(id: '!localpart:server.abc', client: client); + var updateCounter = 0; + final keyVerification = KeyVerification( + client: client, + room: room, + userId: '@alice:example.com', + deviceId: 'ABCD', + onUpdate: () => updateCounter++, + ); + + if (!olmEnabled) return; + + 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(); + }); +} diff --git a/test/markdown_test.dart b/test/markdown_test.dart index 8ceafcd..ff7585d 100644 --- a/test/markdown_test.dart +++ b/test/markdown_test.dart @@ -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 . + */ + import 'package:famedlysdk/src/utils/markdown.dart'; import 'package:test/test.dart'; diff --git a/test/matrix_api_test.dart b/test/matrix_api_test.dart new file mode 100644 index 0000000..0d3fa02 --- /dev/null +++ b/test/matrix_api_test.dart @@ -0,0 +1,1518 @@ +/* + * 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 . + */ + +import 'dart:typed_data'; +import 'package:famedlysdk/matrix_api.dart'; +import 'package:famedlysdk/matrix_api/model/matrix_device_keys.dart'; +import 'package:famedlysdk/matrix_api/model/filter.dart'; +import 'package:famedlysdk/matrix_api/model/matrix_exception.dart'; +import 'package:famedlysdk/matrix_api/model/presence_content.dart'; +import 'package:famedlysdk/matrix_api/model/push_rule_set.dart'; +import 'package:famedlysdk/matrix_api/model/pusher.dart'; +import 'package:test/test.dart'; + +import 'fake_matrix_api.dart'; + +void main() { + /// All Tests related to device keys + group('Matrix API', () { + final matrixApi = MatrixApi( + httpClient: FakeMatrixApi(), + debug: true, + ); + test('MatrixException test', () async { + final exception = MatrixException.fromJson({ + 'flows': [ + { + 'stages': ['example.type.foo'] + } + ], + 'params': { + 'example.type.baz': {'example_key': 'foobar'} + }, + 'session': 'xxxxxxyz', + 'completed': ['example.type.foo'] + }); + expect( + exception.authenticationFlows.first.stages.first, 'example.type.foo'); + expect(exception.authenticationParams['example.type.baz'], + {'example_key': 'foobar'}); + expect(exception.session, 'xxxxxxyz'); + expect(exception.completedAuthenticationFlows, ['example.type.foo']); + expect(exception.requireAdditionalAuthentication, true); + expect(exception.retryAfterMs, null); + expect(exception.error, MatrixError.M_UNKNOWN); + expect(exception.errcode, 'M_FORBIDDEN'); + expect(exception.errorMessage, 'Require additional authentication'); + }); + test('triggerNotFoundError', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + bool error; + error = false; + try { + await matrixApi.request(RequestType.GET, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.POST, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.PUT, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.DELETE, '/fake/path'); + } catch (_) { + error = true; + } + expect(error, true); + error = false; + try { + await matrixApi.request(RequestType.GET, '/path/to/auth/error/'); + } catch (exception) { + expect(exception is MatrixException, true); + expect((exception as MatrixException).errcode, 'M_FORBIDDEN'); + expect((exception as MatrixException).error, MatrixError.M_FORBIDDEN); + expect((exception as MatrixException).errorMessage, 'Blabla'); + expect((exception as MatrixException).requireAdditionalAuthentication, + false); + expect( + (exception as MatrixException).toString(), 'M_FORBIDDEN: Blabla'); + error = true; + } + expect(error, true); + matrixApi.homeserver = null; + }); + test('getSupportedVersions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final supportedVersions = await matrixApi.requestSupportedVersions(); + expect(supportedVersions.versions.contains('r0.5.0'), true); + expect(supportedVersions.unstableFeatures['m.lazy_load_members'], true); + expect(FakeMatrixApi.api['GET']['/client/versions']({}), + supportedVersions.toJson()); + matrixApi.homeserver = null; + }); + test('getWellKnownInformations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final wellKnownInformations = + await matrixApi.requestWellKnownInformations(); + expect(wellKnownInformations.mHomeserver.baseUrl, + 'https://matrix.example.com'); + expect(wellKnownInformations.toJson(), { + 'm.homeserver': {'base_url': 'https://matrix.example.com'}, + 'm.identity_server': {'base_url': 'https://identity.example.com'}, + 'org.example.custom.property': { + 'app_url': 'https://custom.app.example.org' + } + }); + }); + test('getLoginTypes', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginTypes = await matrixApi.requestLoginTypes(); + expect(loginTypes.flows.first.type, 'm.login.password'); + expect(FakeMatrixApi.api['GET']['/client/r0/login']({}), + loginTypes.toJson()); + matrixApi.homeserver = null; + }); + test('login', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginResponse = + await matrixApi.login(userIdentifierType: 'username'); + expect(FakeMatrixApi.api['POST']['/client/r0/login']({}), + loginResponse.toJson()); + matrixApi.homeserver = null; + }); + test('logout', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.logout(); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('logoutAll', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.logoutAll(); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('register', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final registerResponse = + await matrixApi.register(kind: 'guest', username: 'test'); + expect(FakeMatrixApi.api['POST']['/client/r0/register?kind=guest']({}), + registerResponse.toJson()); + matrixApi.homeserver = null; + }); + test('requestEmailToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestEmailToken( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/register/email/requestToken']({}), + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestMsisdnToken( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/register/email/requestToken']({}), + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('changePassword', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.changePassword('1234', auth: { + 'type': 'example.type.foo', + 'session': 'xxxxx', + 'example_credential': 'verypoorsharedsecret' + }); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('resetPasswordUsingEmail', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.resetPasswordUsingEmail( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('resetPasswordUsingMsisdn', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.resetPasswordUsingMsisdn( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deactivateAccount', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi + .deactivateAccount(idServer: 'https://example.com', auth: { + 'type': 'example.type.foo', + 'session': 'xxxxx', + 'example_credential': 'verypoorsharedsecret' + }); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('usernameAvailable', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final loginResponse = await matrixApi.usernameAvailable('testuser'); + expect(loginResponse, true); + matrixApi.homeserver = null; + }); + test('getThirdPartyIdentifiers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestThirdPartyIdentifiers(); + expect(FakeMatrixApi.api['GET']['/client/r0/account/3pid']({}), + {'threepids': response.map((t) => t.toJson()).toList()}); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.addThirdPartyIdentifier('1234', '1234', auth: {}); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('bindThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.bindThirdPartyIdentifier( + '1234', + '1234', + 'https://example.com', + '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.deleteThirdPartyIdentifier( + 'alice@example.com', + ThirdPartyIdentifierMedium.email, + 'https://example.com', + ); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('unbindThirdPartyIdentifier', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.unbindThirdPartyIdentifier( + 'alice@example.com', + ThirdPartyIdentifierMedium.email, + 'https://example.com', + ); + expect(response, IdServerUnbindResult.success); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEmailValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.requestEmailValidationToken( + 'alice@example.com', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.requestMsisdnValidationToken( + 'en', + '1234', + '1234', + 1, + nextLink: 'https://example.com', + idServer: 'https://example.com', + idAccessToken: '1234', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMsisdnValidationToken', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.whoAmI(); + expect(response, 'alice@example.com'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('getServerCapabilities', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.requestServerCapabilities(); + expect(FakeMatrixApi.api['GET']['/client/r0/capabilities']({}), + {'capabilities': response.toJson()}); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('uploadFilter', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = + await matrixApi.uploadFilter('alice@example.com', Filter()); + expect(response, '1234'); + final filter = Filter( + room: RoomFilter( + notRooms: ['!1234'], + rooms: ['!1234'], + ephemeral: StateFilter( + limit: 10, + senders: ['@alice:example.com'], + types: ['type1'], + notTypes: ['type2'], + notRooms: ['!1234'], + notSenders: ['@bob:example.com'], + lazyLoadMembers: true, + includeRedundantMembers: false, + containsUrl: true, + ), + includeLeave: true, + state: StateFilter(), + timeline: StateFilter(), + accountData: StateFilter(limit: 10, types: ['type1']), + ), + presence: EventFilter( + limit: 10, + senders: ['@alice:example.com'], + types: ['type1'], + notRooms: ['!1234'], + notSenders: ['@bob:example.com'], + ), + eventFormat: EventFormat.client, + eventFields: ['type', 'content', 'sender'], + accountData: EventFilter( + types: ['m.accountdatatest'], + notSenders: ['@alice:example.com'], + ), + ); + expect(filter.toJson(), { + 'room': { + 'not_rooms': ['!1234'], + 'rooms': ['!1234'], + 'ephemeral': { + 'limit': 10, + 'types': ['type1'], + 'not_rooms': ['!1234'], + 'not_senders': ['@bob:example.com'], + 'not_types': ['type2'], + 'lazy_load_members': ['type2'], + 'include_redundant_members': ['type2'], + 'contains_url': ['type2'] + }, + 'account_data': { + 'limit': 10, + 'types': ['type1'], + }, + 'include_leave': true, + 'state': {}, + 'timeline': {}, + }, + 'presence': { + 'limit': 10, + 'types': ['type1'], + 'not_rooms': ['!1234'], + 'not_senders': ['@bob:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'], + 'account_data': { + 'types': ['m.accountdatatest'], + 'not_senders': ['@alice:example.com'] + }, + }); + await matrixApi.uploadFilter( + 'alice@example.com', + filter, + ); + final filterMap = { + 'room': { + 'state': { + 'types': ['m.room.*'], + 'not_rooms': ['!726s6s6q:example.com'] + }, + 'timeline': { + 'limit': 10, + 'types': ['m.room.message'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + }, + 'ephemeral': { + 'types': ['m.receipt', 'm.typing'], + 'not_rooms': ['!726s6s6q:example.com'], + 'not_senders': ['@spam:example.com'] + } + }, + 'presence': { + 'types': ['m.presence'], + 'not_senders': ['@alice:example.com'] + }, + 'account_data': { + 'types': ['m.accountdatatest'], + 'not_senders': ['@alice:example.com'] + }, + 'event_format': 'client', + 'event_fields': ['type', 'content', 'sender'] + }; + expect(filterMap, Filter.fromJson(filterMap).toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('downloadFilter', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + await matrixApi.downloadFilter('alice@example.com', '1234'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sync', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + final response = await matrixApi.sync( + filter: '{}', + since: '1234', + fullState: false, + setPresence: PresenceType.unavailable, + timeout: 15, + ); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/sync?filter=%7B%7D&since=1234&full_state=false&set_presence=unavailable&timeout=15']( + {}) as Map, + response.toJson()); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEvent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final event = + await matrixApi.requestEvent('!localpart:server.abc', '1234'); + expect(event.eventId, '143273582443PhrSn:example.org'); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestStateContent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestStateContent( + '!localpart:server.abc', + 'm.room.member', + '@getme:example.com', + ); + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestStates', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestStates('!localpart:server.abc'); + expect(states.length, 4); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMembers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestMembers( + '!localpart:server.abc', + at: '1234', + membership: Membership.join, + notMembership: Membership.leave, + ); + expect(states.length, 1); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestJoinedMembers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final states = await matrixApi.requestJoinedMembers( + '!localpart:server.abc', + ); + expect(states.length, 1); + expect(states['@bar:example.com'].toJson(), { + 'display_name': 'Bar', + 'avatar_url': 'mxc://riot.ovh/printErCATzZijQsSDWorRaK' + }); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMessages', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final timelineHistoryResponse = await matrixApi.requestMessages( + '!localpart:server.abc', + '1234', + Direction.b, + limit: 10, + filter: '{"lazy_load_members":true}', + to: '1234', + ); + + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=1234&dir=b&to=1234&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D']( + {}) as Map, + timelineHistoryResponse.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendState', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.sendState( + '!localpart:server.abc', 'm.room.avatar', {'url': 'mxc://1234'}); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendMessage', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.sendMessage( + '!localpart:server.abc', + 'm.room.message', + '1234', + {'body': 'hello world', 'msgtype': 'm.text'}, + ); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('redact', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final eventId = await matrixApi.redact( + '!localpart:server.abc', + '1234', + '1234', + reason: 'hello world', + ); + + expect(eventId, 'YUwRidLecu:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('createRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = await matrixApi.createRoom( + visibility: Visibility.public, + roomAliasName: '#testroom:example.com', + name: 'testroom', + topic: 'just for testing', + invite: ['@bob:example.com'], + invite3pid: [], + roomVersion: '2', + creationContent: {}, + initialState: [], + preset: CreateRoomPreset.public_chat, + isDirect: false, + powerLevelContentOverride: {}, + ); + + expect(roomId, '!1234:fakeServer.notExisting'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('createRoomAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.createRoomAlias( + '#testalias:example.com', + '!1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAliasInformations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomAliasInformations = + await matrixApi.requestRoomAliasInformations( + '#testalias:example.com', + ); + + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/directory/room/%23testalias%3Aexample.com']({}), + roomAliasInformations.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('removeRoomAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.removeRoomAlias('#testalias:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAliases', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final list = await matrixApi.requestRoomAliases('!localpart:example.com'); + expect(list.length, 3); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestJoinedRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final list = await matrixApi.requestJoinedRooms(); + expect(list.length, 1); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('inviteToRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.inviteToRoom( + '!localpart:example.com', '@bob:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('joinRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!localpart:example.com'; + final response = await matrixApi.joinRoom( + roomId, + thirdPidSignedSender: '@bob:example.com', + thirdPidSignedmxid: '@alice:example.com', + thirdPidSignedToken: '1234', + thirdPidSignedSiganture: { + 'example.org': {'ed25519:0': 'some9signature'} + }, + ); + expect(response, roomId); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('joinRoomOrAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final roomId = '!localpart:example.com'; + final response = await matrixApi.joinRoomOrAlias( + roomId, + servers: ['example.com', 'example.abc'], + thirdPidSignedSender: '@bob:example.com', + thirdPidSignedmxid: '@alice:example.com', + thirdPidSignedToken: '1234', + thirdPidSignedSiganture: { + 'example.org': {'ed25519:0': 'some9signature'} + }, + ); + expect(response, roomId); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('leave', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.leaveRoom('!localpart:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('forget', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.forgetRoom('!localpart:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('kickFromRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.kickFromRoom( + '!localpart:example.com', + '@bob:example.com', + reason: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('banFromRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.banFromRoom( + '!localpart:example.com', + '@bob:example.com', + reason: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('unbanInRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.unbanInRoom( + '!localpart:example.com', + '@bob:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomVisibility', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final visibility = + await matrixApi.requestRoomVisibility('!localpart:example.com'); + expect(visibility, Visibility.public); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setRoomVisibility', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setRoomVisibility( + '!localpart:example.com', Visibility.private); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPublicRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPublicRooms( + limit: 10, + since: '1234', + server: 'example.com', + ); + + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/publicRooms?limit=10&since=1234&server=example.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('searchPublicRooms', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.searchPublicRooms( + limit: 10, + since: '1234', + server: 'example.com', + genericSearchTerm: 'test', + includeAllNetworks: false, + thirdPartyInstanceId: 'id', + ); + + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/publicRooms?server=example.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('searchUser', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.searchUser( + 'test', + limit: 10, + ); + + expect(FakeMatrixApi.api['POST']['/client/r0/user_directory/search']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setDisplayname', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setDisplayname('@alice:example.com', 'Alice M'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDisplayname', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDisplayname('@alice:example.com'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setAvatarUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setAvatarUrl( + '@alice:example.com', + Uri.parse('mxc://test'), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestAvatarUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestAvatarUrl('@alice:example.com'); + expect(response, Uri.parse('mxc://test')); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestProfile', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestProfile('@alice:example.com'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/profile/%40alice%3Aexample.com']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestTurnServerCredentials', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestTurnServerCredentials(); + expect(FakeMatrixApi.api['GET']['/client/r0/voip/turnServer']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendTypingNotification', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendTypingNotification( + '@alice:example.com', + '!localpart:example.com', + true, + timeout: 10, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendReceiptMarker', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendReceiptMarker( + '!localpart:example.com', + '\$1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendReadMarker', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendReadMarker( + '!localpart:example.com', + '\$1234:example.com', + readReceiptLocationEventId: '\$1234:example.com', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendPresence', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendPresence( + '@alice:example.com', + PresenceType.offline, + statusMsg: 'test', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPresence', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPresence( + '@alice:example.com', + ); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/presence/${Uri.encodeComponent('@alice:example.com')}/status']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('upload', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + final response = await matrixApi.upload(Uint8List(0), 'file.jpeg'); + expect(response, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); + var throwsException = false; + try { + await matrixApi.upload(Uint8List(0), 'file.jpg'); + } on MatrixException catch (_) { + throwsException = true; + } + expect(throwsException, true); + matrixApi.homeserver = null; + }); + test('requestOpenGraphDataForUrl', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final openGraphData = await matrixApi.requestOpenGraphDataForUrl( + Uri.parse('https://matrix.org'), + ts: 10, + ); + expect( + FakeMatrixApi.api['GET'] + ['/media/r0/preview_url?url=https%3A%2F%2Fmatrix.org&ts=10']({}), + openGraphData.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestMaxUploadSize', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestMaxUploadSize(); + expect(response, 50000000); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('sendToDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.sendToDevice('m.test', '1234', { + '@alice:example.com': { + 'TLLBEANAAG': {'example_content_key': 'value'} + } + }); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDevices', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final devices = await matrixApi.requestDevices(); + expect(FakeMatrixApi.api['GET']['/client/r0/devices']({})['devices'], + devices.map((i) => i.toJson()).toList()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDevice('QBUAZIFURK'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setDeviceMetadata', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setDeviceMetadata('QBUAZIFURK', displayName: 'test'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteDevice', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deleteDevice('QBUAZIFURK', auth: {}); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deleteDevices', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deleteDevices(['QBUAZIFURK'], auth: {}); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('uploadDeviceKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.uploadDeviceKeys( + deviceKeys: MatrixDeviceKeys( + '@alice:example.com', + 'ABCD', + ['caesar-chiffre'], + {}, + {}, + unsigned: {}, + ), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDeviceKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestDeviceKeys( + { + '@alice:example.com': [], + }, + timeout: 10, + token: '1234', + ); + expect( + response + .deviceKeys['@alice:example.com']['JLAFKJWSCS'].deviceDisplayName, + 'Alices mobile phone'); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/keys/query']({'device_keys': {}}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestOneTimeKeys', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestOneTimeKeys( + { + '@alice:example.com': {'JLAFKJWSCS': 'signed_curve25519'} + }, + timeout: 10, + ); + expect(FakeMatrixApi.api['POST']['/client/r0/keys/claim']({}), + response.toJson()); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestDeviceListsUpdate', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestDeviceListsUpdate('1234', '1234'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushers(); + expect( + FakeMatrixApi.api['GET']['/client/r0/pushers']({}), + {'pushers': response.map((i) => i.toJson()).toList()}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPusher', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPusher( + Pusher( + '1234', + 'app.id', + 'appDisplayName', + 'deviceDisplayName', + 'en', + PusherData( + format: 'event_id_only', url: Uri.parse('https://matrix.org')), + profileTag: 'tag', + kind: 'http', + ), + append: true, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestNotifications', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestNotifications( + from: '1234', + limit: 10, + only: '1234', + ); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/notifications?from=1234&limit=10&only=1234']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRules', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushRules(); + expect( + FakeMatrixApi.api['GET']['/client/r0/pushrules']({}), + {'global': response.toJson()}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestPushRule( + 'global', PushRuleKind.content, 'nocake'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/pushrules/global/content/nocake']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('deletePushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.deletePushRule('global', PushRuleKind.content, 'nocake'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPushRule( + 'global', + PushRuleKind.content, + 'nocake', + [PushRuleAction.notify], + before: '1', + after: '2', + conditions: [ + PushConditions( + 'event_match', + key: 'key', + pattern: 'pattern', + isOperator: '+', + ) + ], + pattern: 'pattern', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRuleEnabled', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final enabled = await matrixApi.requestPushRuleEnabled( + 'global', PushRuleKind.content, 'nocake'); + expect(enabled, true); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('enablePushRule', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.enablePushRule( + 'global', + PushRuleKind.content, + 'nocake', + true, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestPushRuleActions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final actions = await matrixApi.requestPushRuleActions( + 'global', PushRuleKind.content, 'nocake'); + expect(actions.first, PushRuleAction.notify); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setPushRuleActions', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setPushRuleActions( + 'global', + PushRuleKind.content, + 'nocake', + [PushRuleAction.dont_notify], + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('globalSearch', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.globalSearch({}); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('globalSearch', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestEvents( + from: '1234', roomId: '!1234', timeout: 10); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/events?from=1234&timeout=10&roomId=%211234']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomTags', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestRoomTags( + '@alice:example.com', '!localpart:example.com'); + expect( + FakeMatrixApi.api['GET'][ + '/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags']({}), + {'tags': response.map((k, v) => MapEntry(k, v.toJson()))}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addRoomTag', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.addRoomTag( + '@alice:example.com', + '!localpart:example.com', + 'testtag', + order: 0.5, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('addRoomTag', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.removeRoomTag( + '@alice:example.com', + '!localpart:example.com', + 'testtag', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setAccountData( + '@alice:example.com', + 'test.account.data', + {'foo': 'bar'}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestAccountData( + '@alice:example.com', + 'test.account.data', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('setRoomAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.setRoomAccountData( + '@alice:example.com', + '1234', + 'test.account.data', + {'foo': 'bar'}, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestRoomAccountData', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.requestRoomAccountData( + '@alice:example.com', + '1234', + 'test.account.data', + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestWhoIsInfo', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestWhoIsInfo('@alice:example.com'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/admin/whois/%40alice%3Aexample.com']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestEventContext', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestEventContext('1234', '1234', + limit: 10, filter: '{}'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/rooms/1234/context/1234?filter=%7B%7D&limit=10']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('reportEvent', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.reportEvent( + '1234', + '1234', + 'test', + -100, + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestSupportedProtocols', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestSupportedProtocols(); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/protocols']({}), + response.map((k, v) => MapEntry(k, v.toJson())), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestSupportedProtocol', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestSupportedProtocol('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/protocol/irc']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyLocations', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyLocations('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/location/irc']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyUsers', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyUsers('irc'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/user/irc']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyLocationsByAlias', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = + await matrixApi.requestThirdPartyLocationsByAlias('1234'); + expect( + FakeMatrixApi.api['GET'] + ['/client/r0/thirdparty/location?alias=1234']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestThirdPartyUsersByUserId', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestThirdPartyUsersByUserId('1234'); + expect( + FakeMatrixApi.api['GET']['/client/r0/thirdparty/user?userid=1234']({}), + response.map((i) => i.toJson()).toList(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('requestOpenIdCredentials', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + final response = await matrixApi.requestOpenIdCredentials('1234'); + expect( + FakeMatrixApi.api['POST'] + ['/client/r0/user/1234/openid/request_token']({}), + response.toJson(), + ); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + test('upgradeRoom', () async { + matrixApi.homeserver = Uri.parse('https://fakeserver.notexisting'); + matrixApi.accessToken = '1234'; + + await matrixApi.upgradeRoom('1234', '2'); + + matrixApi.homeserver = matrixApi.accessToken = null; + }); + }); +} diff --git a/test/matrix_default_localizations.dart b/test/matrix_default_localizations.dart index 7ec3d6d..7a2f187 100644 --- a/test/matrix_default_localizations.dart +++ b/test/matrix_default_localizations.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:famedlysdk/famedlysdk.dart'; diff --git a/test/matrix_exception_test.dart b/test/matrix_exception_test.dart index 77ec04c..159ed8c 100644 --- a/test/matrix_exception_test.dart +++ b/test/matrix_exception_test.dart @@ -1,26 +1,21 @@ /* - * 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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:http/http.dart'; import 'package:test/test.dart'; diff --git a/test/matrix_file_test.dart b/test/matrix_file_test.dart index 498c230..4be7556 100644 --- a/test/matrix_file_test.dart +++ b/test/matrix_file_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:typed_data'; diff --git a/test/matrix_id_string_extension_test.dart b/test/matrix_id_string_extension_test.dart index f5ae04c..5cacdd8 100644 --- a/test/matrix_id_string_extension_test.dart +++ b/test/matrix_id_string_extension_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:test/test.dart'; diff --git a/test/matrix_localizations_test.dart b/test/matrix_localizations_test.dart index 044073b..fb69475 100644 --- a/test/matrix_localizations_test.dart +++ b/test/matrix_localizations_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:famedlysdk/famedlysdk.dart'; import 'package:test/test.dart'; diff --git a/test/mxc_uri_extension_test.dart b/test/mxc_uri_extension_test.dart index 5d5afdb..798854c 100644 --- a/test/mxc_uri_extension_test.dart +++ b/test/mxc_uri_extension_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:test/test.dart'; @@ -31,21 +26,20 @@ void main() { /// All Tests related to the MxContent group('MxContent', () { test('Formatting', () async { - var client = Client('testclient'); - client.httpClient = FakeMatrixApi(); + var client = Client('testclient', httpClient: FakeMatrixApi()); await client.checkServer('https://fakeserver.notexisting'); final mxc = 'mxc://exampleserver.abc/abcdefghijklmn'; final content = Uri.parse(mxc); expect(content.isScheme('mxc'), true); expect(content.getDownloadLink(client), - '${client.homeserver}/_matrix/media/r0/download/exampleserver.abc/abcdefghijklmn'); + '${client.api.homeserver.toString()}/_matrix/media/r0/download/exampleserver.abc/abcdefghijklmn'); expect(content.getThumbnail(client, width: 50, height: 50), - '${client.homeserver}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop'); + '${client.api.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.homeserver}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale'); + '${client.api.homeserver.toString()}/_matrix/media/r0/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale'); }); }); } diff --git a/test/presence_test.dart b/test/presence_test.dart deleted file mode 100644 index a24c3ce..0000000 --- a/test/presence_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:test/test.dart'; - -void main() { - /// All Tests related to the ChatTime - group('Presence', () { - test('fromJson', () async { - var rawPresence = { - 'content': { - 'avatar_url': 'mxc://example.org/wefuiwegh8742w', - 'currently_active': false, - 'last_active_ago': 2478593, - 'presence': 'online', - 'status_msg': 'Making cupcakes' - }, - 'sender': '@example:localhost', - 'type': 'm.presence' - }; - var presence = Presence.fromJson(rawPresence); - expect(presence.sender, '@example:localhost'); - expect(presence.avatarUrl.toString(), 'mxc://example.org/wefuiwegh8742w'); - expect(presence.currentlyActive, false); - expect(presence.lastActiveAgo, 2478593); - expect(presence.presence, PresenceType.online); - expect(presence.statusMsg, 'Making cupcakes'); - }); - }); -} diff --git a/test/public_rooms_response_test.dart b/test/public_rooms_response_test.dart deleted file mode 100644 index b76a7d4..0000000 --- a/test/public_rooms_response_test.dart +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:test/test.dart'; - -import 'fake_matrix_api.dart'; - -void main() { - /// All Tests related to device keys - group('Public Rooms Response', () { - Client client; - test('Public Rooms Response', () async { - client = Client('testclient', debug: true); - client.httpClient = FakeMatrixApi(); - - await client.checkServer('https://fakeServer.notExisting'); - final responseMap = { - 'chunk': [ - { - 'aliases': ['#murrays:cheese.bar'], - 'avatar_url': 'mxc://bleeker.street/CHEDDARandBRIE', - 'guest_can_join': false, - 'name': 'CHEESE', - 'num_joined_members': 37, - 'room_id': '1234', - 'topic': 'Tasty tasty cheese', - 'world_readable': true - } - ], - 'next_batch': 'p190q', - 'prev_batch': 'p1902', - 'total_room_count_estimate': 115 - }; - final publicRoomsResponse = - PublicRoomsResponse.fromJson(responseMap, client); - await publicRoomsResponse.publicRooms.first.join(); - }); - }); -} diff --git a/test/push_rules_test.dart b/test/push_rules_test.dart deleted file mode 100644 index 37e35f1..0000000 --- a/test/push_rules_test.dart +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ - -import 'package:famedlysdk/src/utils/push_rules.dart'; -import 'package:test/test.dart'; - -void main() { - /// All Tests related to the MxContent - group('PushRules', () { - test('Create', () async { - final json = { - 'global': { - 'content': [ - { - 'actions': [ - 'notify', - {'set_tweak': 'sound', 'value': 'default'}, - {'set_tweak': 'highlight'} - ], - 'default': true, - 'enabled': true, - 'pattern': 'alice', - 'rule_id': '.m.rule.contains_user_name' - } - ], - 'override': [ - { - 'actions': ['dont_notify'], - 'conditions': [], - 'default': true, - 'enabled': false, - 'rule_id': '.m.rule.master' - }, - { - 'actions': ['dont_notify'], - 'conditions': [ - { - 'key': 'content.msgtype', - 'kind': 'event_match', - 'pattern': 'm.notice' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.suppress_notices' - } - ], - 'room': [], - 'sender': [], - 'underride': [ - { - 'actions': [ - 'notify', - {'set_tweak': 'sound', 'value': 'ring'}, - {'set_tweak': 'highlight', 'value': false} - ], - 'conditions': [ - { - 'key': 'type', - 'kind': 'event_match', - 'pattern': 'm.call.invite' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.call' - }, - { - 'actions': [ - 'notify', - {'set_tweak': 'sound', 'value': 'default'}, - {'set_tweak': 'highlight'} - ], - 'conditions': [ - {'kind': 'contains_display_name'} - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.contains_display_name' - }, - { - 'actions': [ - 'notify', - {'set_tweak': 'sound', 'value': 'default'}, - {'set_tweak': 'highlight', 'value': false} - ], - 'conditions': [ - {'kind': 'room_member_count', 'is': '2'}, - { - 'kind': 'event_match', - 'key': 'type', - 'pattern': 'm.room.message' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.room_one_to_one' - }, - { - 'actions': [ - 'notify', - {'set_tweak': 'sound', 'value': 'default'}, - {'set_tweak': 'highlight', 'value': false} - ], - 'conditions': [ - { - 'key': 'type', - 'kind': 'event_match', - 'pattern': 'm.room.member' - }, - { - 'key': 'content.membership', - 'kind': 'event_match', - 'pattern': 'invite' - }, - { - 'key': 'state_key', - 'kind': 'event_match', - 'pattern': '@alice:example.com' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.invite_for_me' - }, - { - 'actions': [ - 'notify', - {'set_tweak': 'highlight', 'value': false} - ], - 'conditions': [ - { - 'key': 'type', - 'kind': 'event_match', - 'pattern': 'm.room.member' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.member_event' - }, - { - 'actions': [ - 'notify', - {'set_tweak': 'highlight', 'value': false} - ], - 'conditions': [ - { - 'key': 'type', - 'kind': 'event_match', - 'pattern': 'm.room.message' - } - ], - 'default': true, - 'enabled': true, - 'rule_id': '.m.rule.message' - } - ] - } - }; - - expect(PushRules.fromJson(json) != null, true); - }); - }); -} diff --git a/test/pusher_test.dart b/test/pusher_test.dart deleted file mode 100644 index fe2dfa2..0000000 --- a/test/pusher_test.dart +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ -import 'package:famedlysdk/src/utils/pusher.dart'; -import 'package:test/test.dart'; - -void main() { - /// All Tests related to device keys - group('Pusher', () { - test('Pusher', () { - final rawPusher = { - 'pushkey': 'Xp/MzCt8/9DcSNE9cuiaoT5Ac55job3TdLSSmtmYl4A=', - 'kind': 'http', - 'app_id': 'face.mcapp.appy.prod', - 'app_display_name': 'Appy McAppface', - 'device_display_name': "Alice's Phone", - 'profile_tag': 'xyz', - 'lang': 'en-US', - 'data': {'url': 'https://example.com/_matrix/push/v1/notify'} - }; - - final pusher = Pusher.fromJson(rawPusher); - expect(pusher.toJson(), rawPusher); - }); - }); -} diff --git a/test/room_key_request_test.dart b/test/room_key_request_test.dart index 5ff83c3..6801c62 100644 --- a/test/room_key_request_test.dart +++ b/test/room_key_request_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'dart:convert'; @@ -62,9 +57,16 @@ void main() { expect(toDeviceEvent.content, rawJson['content']); expect(toDeviceEvent.sender, rawJson['sender']); expect(toDeviceEvent.type, rawJson['type']); + expect( + ToDeviceEventDecryptionError( + exception: Exception('test'), + stackTrace: null, + toDeviceEvent: toDeviceEvent) + .sender, + rawJson['sender'], + ); - var matrix = Client('testclient', debug: true); - matrix.httpClient = FakeMatrixApi(); + var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); @@ -91,8 +93,7 @@ void main() { await matrix.dispose(closeDatabase: true); }); test('Create Request', () async { - var matrix = Client('testclient', debug: true); - matrix.httpClient = FakeMatrixApi(); + var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); @@ -124,8 +125,7 @@ void main() { }); final validSessionId = 'ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU'; test('Reply To Request', () async { - var matrix = Client('testclient', debug: true); - matrix.httpClient = FakeMatrixApi(); + var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); @@ -135,8 +135,12 @@ void main() { } 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); + await matrix.userDeviceKeys['@alice:example.com'].deviceKeys['OTHERDEVICE'] + .startVerification(matrix); // test a successful share var event = ToDeviceEvent( sender: '@alice:example.com', @@ -268,8 +272,7 @@ void main() { await matrix.dispose(closeDatabase: true); }); test('Receive shared keys', () async { - var matrix = Client('testclient', debug: true); - matrix.httpClient = FakeMatrixApi(); + var matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); matrix.database = getDatabase(); await matrix.checkServer('https://fakeServer.notExisting'); await matrix.login('test', '1234'); diff --git a/test/room_test.dart b/test/room_test.dart index fdc6fe9..2adcb98 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -1,26 +1,22 @@ /* - * 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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ +import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/event.dart'; import 'package:famedlysdk/src/room.dart'; @@ -41,8 +37,7 @@ void main() { /// All Tests related to the Event group('Room', () { test('Login', () async { - matrix = Client('testclient', debug: true); - matrix.httpClient = FakeMatrixApi(); + matrix = Client('testclient', debug: true, httpClient: FakeMatrixApi()); final checkResp = await matrix.checkServer('https://fakeServer.notExisting'); @@ -123,7 +118,7 @@ void main() { room.states['m.room.canonical_alias'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.canonical_alias', + type: 'm.room.canonical_alias', roomId: room.id, room: room, eventId: '123', @@ -134,7 +129,7 @@ void main() { room.states['m.room.name'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.name', + type: 'm.room.name', roomId: room.id, room: room, eventId: '123', @@ -145,7 +140,7 @@ void main() { expect(room.topic, ''); room.states['m.room.topic'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.topic', + type: 'm.room.topic', roomId: room.id, room: room, eventId: '123', @@ -156,7 +151,7 @@ void main() { expect(room.avatar, null); room.states['m.room.avatar'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.avatar', + type: 'm.room.avatar', roomId: room.id, room: room, eventId: '123', @@ -165,22 +160,26 @@ void main() { expect(room.avatar.toString(), 'mxc://testurl'); room.states['m.room.message'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.message', + type: 'm.room.message', roomId: room.id, room: room, eventId: '12345', - time: DateTime.now(), + originServerTs: DateTime.now(), content: {'msgtype': 'm.text', 'body': 'test'}, stateKey: ''); expect(room.lastEvent.eventId, '12345'); expect(room.lastMessage, 'test'); - expect(room.timeCreated, room.lastEvent.time); + expect(room.timeCreated, room.lastEvent.originServerTs); }); test('sendReadReceipt', () async { await room.sendReadReceipt('ยง1234:fakeServer.notExisting'); }); + test('enableEncryption', () async { + await room.enableEncryption(); + }); + test('requestParticipants', () async { final participants = await room.requestParticipants(); expect(participants.length, 1); @@ -222,7 +221,7 @@ void main() { test('PowerLevels', () async { room.states['m.room.power_levels'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.power_levels', + type: 'm.room.power_levels', roomId: room.id, room: room, eventId: '123', @@ -258,7 +257,7 @@ void main() { room.states['m.room.power_levels'] = Event( senderId: '@test:example.com', - typeKey: 'm.room.power_levels', + type: 'm.room.power_levels', roomId: room.id, room: room, eventId: '123abc', @@ -298,16 +297,16 @@ void main() { test('getParticipants', () async { room.setState(Event( senderId: '@alice:test.abc', - typeKey: 'm.room.member', + type: 'm.room.member', roomId: room.id, room: room, eventId: '12345', - time: DateTime.now(), + originServerTs: DateTime.now(), content: {'displayname': 'alice'}, stateKey: '@alice:test.abc')); final userList = room.getParticipants(); - expect(userList.length, 5); - expect(userList[3].displayName, 'Alice Margatroid'); + expect(userList.length, 4); + expect(userList[3].displayName, 'alice'); }); test('addToDirectChat', () async { @@ -325,7 +324,7 @@ void main() { user = await room.getUserByMXID('@getme:example.com'); } catch (_) {} expect(user.stateKey, '@getme:example.com'); - expect(user.calcDisplayname(), 'You got me'); + expect(user.calcDisplayname(), 'Getme'); }); test('setAvatar', () async { @@ -375,11 +374,11 @@ void main() { room.setState( Event( senderId: '@alice:test.abc', - typeKey: 'm.room.encryption', + type: 'm.room.encryption', roomId: room.id, room: room, eventId: '12345', - time: DateTime.now(), + originServerTs: DateTime.now(), content: { 'algorithm': 'm.megolm.v1.aes-sha2', 'rotation_period_ms': 604800000, @@ -433,18 +432,83 @@ void main() { var encryptedEvent = Event( content: encryptedPayload, - typeKey: 'm.room.encrypted', + type: 'm.room.encrypted', senderId: room.client.userID, eventId: '1234', roomId: room.id, room: room, - time: DateTime.now(), + originServerTs: DateTime.now(), ); var decryptedEvent = room.decryptGroupMessage(encryptedEvent); - expect(decryptedEvent.typeKey, 'm.room.message'); + expect(decryptedEvent.type, 'm.room.message'); expect(decryptedEvent.content, payload); }); + test('setPushRuleState', () async { + await room.setPushRuleState(PushRuleState.notify); + await room.setPushRuleState(PushRuleState.dont_notify); + await room.setPushRuleState(PushRuleState.mentions_only); + await room.setPushRuleState(PushRuleState.notify); + }); + + test('Test call methods', () async { + await room.inviteToCall('1234', 1234, 'sdp', txid: '1234'); + await room.answerCall('1234', 'sdp', txid: '1234'); + await room.hangupCall('1234', txid: '1234'); + await room.sendCallCandidates('1234', [], txid: '1234'); + }); + + test('joinRules', () async { + expect(room.canChangeJoinRules, false); + expect(room.joinRules, JoinRules.public); + room.setState(Event.fromJson({ + 'content': {'join_rule': 'invite'}, + '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(room.joinRules, JoinRules.invite); + await room.setJoinRules(JoinRules.invite); + }); + + test('guestAccess', () async { + expect(room.canChangeGuestAccess, false); + expect(room.guestAccess, GuestAccess.forbidden); + room.setState(Event.fromJson({ + 'content': {'guest_access': 'can_join'}, + 'event_id': '\$143273582443PhrSn:example.org', + 'origin_server_ts': 1432735824653, + 'room_id': '!jEsUZKDJdhlrceRyVU:example.org', + 'sender': '@example:example.org', + 'state_key': '', + 'type': 'm.room.guest_access', + 'unsigned': {'age': 1234} + }, room)); + expect(room.guestAccess, GuestAccess.can_join); + await room.setGuestAccess(GuestAccess.can_join); + }); + + test('historyVisibility', () async { + expect(room.canChangeHistoryVisibility, false); + expect(room.historyVisibility, null); + room.setState(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(room.historyVisibility, HistoryVisibility.shared); + await room.setHistoryVisibility(HistoryVisibility.joined); + }); + test('logout', () async { await matrix.logout(); }); diff --git a/test/session_key_test.dart b/test/session_key_test.dart new file mode 100644 index 0000000..9540cae --- /dev/null +++ b/test/session_key_test.dart @@ -0,0 +1,56 @@ +/* + * 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 . + */ +import 'dart:convert'; +import 'package:famedlysdk/src/utils/session_key.dart'; +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + +void main() { + /// All Tests related to the ChatTime + group('SessionKey', () { + var olmEnabled = true; + try { + olm.init(); + olm.Account(); + } catch (_) { + olmEnabled = false; + print('[LibOlm] Failed to load LibOlm: ' + _.toString()); + } + print('[LibOlm] Enabled: $olmEnabled'); + test('SessionKey test', () { + if (olmEnabled) { + final sessionKey = SessionKey( + content: { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': '!Cuyf34gef24t:localhost', + 'session_id': 'X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ', + 'session_key': + 'AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY...' + }, + inboundGroupSession: olm.InboundGroupSession(), + key: '1234', + indexes: {}, + ); + expect(sessionKey.senderClaimedEd25519Key, ''); + expect(sessionKey.toJson(), + SessionKey.fromJson(sessionKey.toJson(), '1234').toJson()); + expect(sessionKey.toString(), json.encode(sessionKey.toJson())); + } + }); + }); +} diff --git a/test/states_map_test.dart b/test/states_map_test.dart index 51fefc5..5e0aa7d 100644 --- a/test/states_map_test.dart +++ b/test/states_map_test.dart @@ -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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ import 'package:famedlysdk/famedlysdk.dart'; @@ -33,7 +28,7 @@ void main() { states['m.room.name'] = Event( eventId: '1', content: {'name': 'test'}, - typeKey: 'm.room.name', + type: 'm.room.name', stateKey: '', roomId: '!test:test.test', senderId: '@alice:test.test'); @@ -41,7 +36,7 @@ void main() { states['@alice:test.test'] = Event( eventId: '2', content: {'membership': 'join'}, - typeKey: 'm.room.name', + type: 'm.room.name', stateKey: '@alice:test.test', roomId: '!test:test.test', senderId: '@alice:test.test'); @@ -49,7 +44,7 @@ void main() { states['m.room.member']['@bob:test.test'] = Event( eventId: '3', content: {'membership': 'join'}, - typeKey: 'm.room.name', + type: 'm.room.name', stateKey: '@bob:test.test', roomId: '!test:test.test', senderId: '@bob:test.test'); @@ -57,7 +52,7 @@ void main() { states['com.test.custom'] = Event( eventId: '4', content: {'custom': 'stuff'}, - typeKey: 'com.test.custom', + type: 'com.test.custom', stateKey: 'customStateKey', roomId: '!test:test.test', senderId: '@bob:test.test'); diff --git a/test/timeline_test.dart b/test/timeline_test.dart index e2cda2e..689c4a1 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -1,34 +1,28 @@ /* - * 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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ -import 'package:famedlysdk/src/room_account_data.dart'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:test/test.dart'; import 'package:famedlysdk/src/client.dart'; import 'package:famedlysdk/src/room.dart'; import 'package:famedlysdk/src/timeline.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/utils/event_update.dart'; +import 'package:famedlysdk/src/utils/room_update.dart'; import 'fake_matrix_api.dart'; void main() { @@ -39,8 +33,7 @@ void main() { var updateCount = 0; var insertList = []; - var client = Client('testclient', debug: true); - client.httpClient = FakeMatrixApi(); + var client = Client('testclient', debug: true, httpClient: FakeMatrixApi()); var room = Room( id: roomID, client: client, prev_batch: '1234', roomAccountData: {}); @@ -94,15 +87,16 @@ void main() { expect(timeline.events.length, 2); expect(timeline.events[0].eventId, '1'); expect(timeline.events[0].sender.id, '@alice:example.com'); - expect(timeline.events[0].time.millisecondsSinceEpoch, testTimeStamp); + expect(timeline.events[0].originServerTs.millisecondsSinceEpoch, + testTimeStamp); expect(timeline.events[0].body, 'Testcase'); expect( - timeline.events[0].time.millisecondsSinceEpoch > - timeline.events[1].time.millisecondsSinceEpoch, + timeline.events[0].originServerTs.millisecondsSinceEpoch > + timeline.events[1].originServerTs.millisecondsSinceEpoch, true); expect(timeline.events[0].receipts, []); - room.roomAccountData['m.receipt'] = RoomAccountData.fromJson({ + room.roomAccountData['m.receipt'] = BasicRoomEvent.fromJson({ 'type': 'm.receipt', 'content': { '@alice:example.com': { @@ -111,7 +105,7 @@ void main() { } }, 'room_id': roomID, - }, room); + }); await Future.delayed(Duration(milliseconds: 50)); @@ -241,6 +235,7 @@ void main() { expect(timeline.events[7].eventId, '2143273582443PhrSn:example.org'); expect(timeline.events[8].eventId, '1143273582443PhrSn:example.org'); expect(room.prev_batch, 't47409-4357353_219380_26003_2265'); + await timeline.events[8].redact(reason: 'test', txid: '1234'); }); test('Clear cache on limited timeline', () async { diff --git a/test/user_test.dart b/test/user_test.dart index 0d56f43..13e67b2 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -1,33 +1,48 @@ /* - * 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 - * Marcel Radzio + * 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. + * 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. * - * 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 . + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . */ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/matrix_api.dart'; import 'package:famedlysdk/src/event.dart'; import 'package:famedlysdk/src/user.dart'; import 'package:test/test.dart'; +import 'fake_matrix_api.dart'; + void main() { /// All Tests related to the Event group('User', () { + var client = Client('testclient', debug: true, httpClient: FakeMatrixApi()); + final user1 = User( + '@alice:example.com', + membership: 'join', + displayName: 'Alice M', + avatarUrl: 'mxc://bla', + room: Room(id: '!localpart:server.abc', client: client), + ); + test('create', () async { + expect(user1.powerLevel, 0); + expect(user1.stateKey, '@alice:example.com'); + expect(user1.id, '@alice:example.com'); + expect(user1.membership, Membership.join); + expect(user1.avatarUrl.toString(), 'mxc://bla'); + expect(user1.displayName, 'Alice M'); + }); test('Create from json', () async { final id = '@alice:server.abc'; final membership = Membership.join; @@ -65,6 +80,56 @@ void main() { expect(user1.calcDisplayname(), 'Alice'); expect(user2.calcDisplayname(), 'SuperAlice'); expect(user3.calcDisplayname(), 'Alice Mep'); + expect(user3.calcDisplayname(formatLocalpart: false), 'alice_mep'); }); + test('kick', () async { + await client.checkServer('https://fakeserver.notexisting'); + await user1.kick(); + }); + test('ban', () async { + await client.checkServer('https://fakeserver.notexisting'); + await user1.ban(); + }); + test('unban', () async { + await client.checkServer('https://fakeserver.notexisting'); + await user1.unban(); + }); + test('setPower', () async { + await client.checkServer('https://fakeserver.notexisting'); + await user1.setPower(50); + }); + test('startDirectChat', () async { + await client.checkServer('https://fakeserver.notexisting'); + await client.login('test', '1234'); + await user1.startDirectChat(); + }); + test('getPresence', () async { + await client.checkServer('https://fakeserver.notexisting'); + await client.handleSync(SyncUpdate.fromJson({ + 'presence': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.presence', + 'content': {'presence': 'online'} + } + ] + } + })); + expect(user1.presence.presence.presence, PresenceType.online); + }); + test('canBan', () async { + await client.checkServer('https://fakeserver.notexisting'); + expect(user1.canBan, false); + }); + test('canKick', () async { + await client.checkServer('https://fakeserver.notexisting'); + expect(user1.canKick, false); + }); + test('canChangePowerLevel', () async { + await client.checkServer('https://fakeserver.notexisting'); + expect(user1.canChangePowerLevel, false); + }); + client.dispose(); }); } diff --git a/test/well_known_informations_test.dart b/test/well_known_informations_test.dart deleted file mode 100644 index 7417d10..0000000 --- a/test/well_known_informations_test.dart +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019 Zender & Kurtz GbR. - * - * Authors: - * Christian Pauly - * Marcel Radzio - * - * 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 . - */ -import 'package:famedlysdk/famedlysdk.dart'; -import 'package:test/test.dart'; - -void main() { - /// All Tests related to device keys - group('WellKnownInformations', () { - test('WellKnownInformations', () { - final json = { - 'm.homeserver': {'base_url': 'https://matrix.example.com'}, - 'm.identity_server': {'base_url': 'https://identity.example.com'}, - 'org.example.custom.property': { - 'app_url': 'https://custom.app.example.org' - } - }; - WellKnownInformations.fromJson(json); - }); - }); -} diff --git a/test_driver/famedlysdk_test.dart b/test_driver/famedlysdk_test.dart index 5e9daed..6ac0d09 100644 --- a/test_driver/famedlysdk_test.dart +++ b/test_driver/famedlysdk_test.dart @@ -1,4 +1,5 @@ import 'package:famedlysdk/famedlysdk.dart'; +import 'package:famedlysdk/matrix_api.dart'; import '../test/fake_database.dart'; void main() => test(); @@ -17,14 +18,14 @@ const String testMessage6 = 'Hello mars'; void test() async { print('++++ Login $testUserA ++++'); - var testClientA = Client('TestClient', debug: false); + var testClientA = Client('TestClientA', debug: false); testClientA.database = getDatabase(); await testClientA.checkServer(homeserver); await testClientA.login(testUserA, testPasswordA); assert(testClientA.encryptionEnabled); print('++++ Login $testUserB ++++'); - var testClientB = Client('TestClient', debug: false); + var testClientB = Client('TestClientB', debug: false); testClientB.database = getDatabase(); await testClientB.checkServer(homeserver); await testClientB.login(testUserB, testPasswordA); @@ -39,9 +40,7 @@ void test() async { try { await room.leave(); await room.forget(); - } catch (e) { - print(e); - } + } catch (_) {} } print('++++ ($testUserB) Leave all rooms ++++'); @@ -51,9 +50,7 @@ void test() async { try { await room.leave(); await room.forget(); - } catch (e) { - print(e); - } + } catch (_) {} } } @@ -74,7 +71,7 @@ void test() async { .userDeviceKeys[testUserB].deviceKeys[testClientB.deviceID].blocked); print('++++ ($testUserA) Create room and invite $testUserB ++++'); - await testClientA.createRoom(invite: [User(testUserB)]); + await testClientA.api.createRoom(invite: [testUserB]); await Future.delayed(Duration(seconds: 1)); var room = testClientA.rooms.first; assert(room != null); @@ -174,12 +171,11 @@ void test() async { "++++ ($testUserA) Received decrypted message: '${room.lastMessage}' ++++"); print('++++ Login $testUserB in another client ++++'); - var testClientC = Client('TestClient', debug: false); - testClientC.database = getDatabase(); + var testClientC = + Client('TestClientC', debug: false, database: getDatabase()); await testClientC.checkServer(homeserver); await testClientC.login(testUserB, testPasswordA); await Future.delayed(Duration(seconds: 3)); - assert(room.outboundGroupSession == null); print("++++ ($testUserA) Send again encrypted message: '$testMessage4' ++++"); await room.sendTextEvent(testMessage4); @@ -202,11 +198,10 @@ void test() async { "++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++"); print('++++ Logout $testUserB another client ++++'); + await testClientC.dispose(); await testClientC.logout(); testClientC = null; await Future.delayed(Duration(seconds: 5)); - assert(room.outboundGroupSession == null); - assert(inviteRoom.outboundGroupSession == null); print("++++ ($testUserA) Send again encrypted message: '$testMessage6' ++++"); await room.sendTextEvent(testMessage6); @@ -224,11 +219,14 @@ void test() async { print( "++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++"); - print('++++ ($testUserA) Restore user ++++'); - final clientADatabase = testClientA.database; +/* print('++++ ($testUserA) Restore user ++++'); + await testClientA.dispose(); testClientA = null; - testClientA = Client('TestClient', debug: false); - testClientA.database = clientADatabase; + testClientA = Client( + 'TestClientA', + debug: false, + database: getDatabase(), + ); testClientA.connect(); await Future.delayed(Duration(seconds: 3)); var restoredRoom = testClientA.rooms.first; @@ -255,14 +253,11 @@ void test() async { assert(testClientB.olmSessions[testClientA.identityKey].length == 1); assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() == testClientB.olmSessions[testClientA.identityKey].first.session_id()); - /*assert(restoredRoom.outboundGroupSession.session_id() == currentSessionIdA); - assert(inviteRoom.inboundGroupSessions - .containsKey(restoredRoom.outboundGroupSession.session_id()));*/ assert(restoredRoom.lastMessage == testMessage5); assert(inviteRoom.lastMessage == testMessage5); assert(testClientB.getRoomById(roomId).lastMessage == testMessage5); print( - "++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++"); + "++++ ($testUserB) Received decrypted message: '${inviteRoom.lastMessage}' ++++");*/ print('++++ Logout $testUserA and $testUserB ++++'); await room.leave(); @@ -270,10 +265,10 @@ void test() async { await inviteRoom.leave(); await inviteRoom.forget(); await Future.delayed(Duration(seconds: 1)); - await testClientA.jsonRequest( - type: HTTPType.POST, action: '/client/r0/logout/all'); - await testClientB.jsonRequest( - type: HTTPType.POST, action: '/client/r0/logout/all'); + await testClientA.dispose(); + await testClientB.dispose(); + await testClientA.api.logoutAll(); + await testClientB.api.logoutAll(); testClientA = null; testClientB = null; return;