diff --git a/lib/src/client.dart b/lib/src/client.dart index 1db5e395..f185ac06 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -25,7 +25,6 @@ import 'dart:typed_data'; import 'package:async/async.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:http/http.dart' as http; -import 'package:http/http.dart'; import 'package:mime/mime.dart'; import 'package:olm/olm.dart' as olm; import 'package:random_string/random_string.dart'; @@ -124,6 +123,24 @@ class Client extends MatrixApi { /// The timeout until a typing indicator gets removed automatically. final Duration typingIndicatorTimeout; + DiscoveryInformation? _wellKnown; + + /// the cached .well-known file updated using [getWellknown] + DiscoveryInformation? get wellKnown => _wellKnown; + + /// The homeserver this client is communicating with. + /// + /// In case the [homeserver]'s host differs from the previous value, the + /// [wellKnown] cache will be invalidated. + @override + set homeserver(Uri? homeserver) { + if (homeserver?.host != this.homeserver?.host) { + _wellKnown = null; + unawaited(database?.storeWellKnown(null)); + } + super.homeserver = homeserver; + } + Future Function( MatrixImageFileResizeArguments)? customImageResizer; @@ -531,6 +548,27 @@ class Client extends MatrixApi { } } + /// Gets discovery information about the domain. The file may include + /// additional keys, which MUST follow the Java package naming convention, + /// e.g. `com.example.myapp.property`. This ensures property names are + /// suitably namespaced for each application and reduces the risk of + /// clashes. + /// + /// Note that this endpoint is not necessarily handled by the homeserver, + /// but by another webserver, to be used for discovering the homeserver URL. + /// + /// The result of this call is stored in [wellKnown] for later use at runtime. + @override + Future getWellknown() async { + final wellKnown = await super.getWellknown(); + + // do not reset the well known here, so super call + super.homeserver = wellKnown.mHomeserver.baseUrl.stripTrailingSlash(); + _wellKnown = wellKnown; + await database?.storeWellKnown(wellKnown); + return wellKnown; + } + /// 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 [checkHomeserver] first to set a homeserver. @@ -1196,7 +1234,7 @@ class Client extends MatrixApi { path = '_matrix/media/v3/config'; } final requestUri = Uri(path: path); - final request = Request('GET', baseUri!.resolveUri(requestUri)); + final request = http.Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1236,7 +1274,7 @@ class Client extends MatrixApi { // removed with msc3916, so just to be explicit 'allow_remote': allowRemote.toString(), }); - final request = Request('GET', baseUri!.resolveUri(requestUri)); + final request = http.Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1279,7 +1317,7 @@ class Client extends MatrixApi { // removed with msc3916, so just to be explicit 'allow_remote': allowRemote.toString(), }); - final request = Request('GET', baseUri!.resolveUri(requestUri)); + final request = http.Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1315,7 +1353,7 @@ class Client extends MatrixApi { 'url': url.toString(), if (ts != null) 'ts': ts.toString(), }); - final request = Request('GET', baseUri!.resolveUri(requestUri)); + final request = http.Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1369,7 +1407,7 @@ class Client extends MatrixApi { 'allow_remote': allowRemote.toString(), }); - final request = Request('GET', baseUri!.resolveUri(requestUri)); + final request = http.Request('GET', baseUri!.resolveUri(requestUri)); request.headers['authorization'] = 'Bearer ${bearerToken!}'; final response = await httpClient.send(request); final responseBody = await response.stream.toBytes(); @@ -1956,12 +1994,16 @@ class Client extends MatrixApi { _accountData = data; _updatePushrules(); }); + _discoveryDataLoading = database.getWellKnown().then((data) { + _wellKnown = data; + }); // ignore: deprecated_member_use_from_same_package presences.clear(); if (waitUntilLoadCompletedLoaded) { await userDeviceKeysLoading; await roomsLoading; await _accountDataLoading; + await _discoveryDataLoading; } } _initLock = false; @@ -2784,10 +2826,13 @@ class Client extends MatrixApi { Future? userDeviceKeysLoading; Future? roomsLoading; Future? _accountDataLoading; + Future? _discoveryDataLoading; Future? firstSyncReceived; Future? get accountDataLoading => _accountDataLoading; + Future? get wellKnownLoading => _discoveryDataLoading; + /// A map of known device keys per user. Map get userDeviceKeys => _userDeviceKeys; Map _userDeviceKeys = {}; @@ -3593,6 +3638,7 @@ class SdkError { class SyncConnectionException implements Exception { final Object originalException; + SyncConnectionException(this.originalException); } diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index b4abfd6a..d8bc3cf4 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -27,7 +27,9 @@ import 'package:matrix/src/utils/queued_to_device_event.dart'; abstract class DatabaseApi { int get maxFileSize => 1 * 1024 * 1024; + bool get supportsFileStoring => false; + Future?> getClient(String name); Future updateClient( @@ -334,6 +336,10 @@ abstract class DatabaseApi { Future getPresence(String userId); + Future storeWellKnown(DiscoveryInformation? discoveryInformation); + + Future getWellKnown(); + /// Deletes the whole database. The database needs to be created again after /// this. Future delete(); diff --git a/lib/src/database/hive_collections_database.dart b/lib/src/database/hive_collections_database.dart index c8454aa5..80b98d93 100644 --- a/lib/src/database/hive_collections_database.dart +++ b/lib/src/database/hive_collections_database.dart @@ -1595,6 +1595,25 @@ class HiveCollectionsDatabase extends DatabaseApi { } } + @override + Future storeWellKnown(DiscoveryInformation? discoveryInformation) { + if (discoveryInformation == null) { + return _clientBox.delete('discovery_information'); + } + return _clientBox.put( + 'discovery_information', + jsonEncode(discoveryInformation.toJson()), + ); + } + + @override + Future getWellKnown() async { + final rawDiscoveryInformation = + await _clientBox.get('discovery_information'); + if (rawDiscoveryInformation == null) return null; + return DiscoveryInformation.fromJson(jsonDecode(rawDiscoveryInformation)); + } + @override Future delete() => _collection.deleteFromDisk(); diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index caa1e4d7..8086d7f3 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -1401,6 +1401,25 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin { throw UnimplementedError(); } + @override + Future storeWellKnown(DiscoveryInformation? discoveryInformation) { + if (discoveryInformation == null) { + return _clientBox.delete('discovery_information'); + } + return _clientBox.put( + 'discovery_information', + jsonEncode(discoveryInformation.toJson()), + ); + } + + @override + Future getWellKnown() async { + final rawDiscoveryInformation = + await _clientBox.get('discovery_information'); + if (rawDiscoveryInformation == null) return null; + return DiscoveryInformation.fromJson(jsonDecode(rawDiscoveryInformation)); + } + @override Future delete() => Hive.deleteFromDisk(); diff --git a/lib/src/database/matrix_sdk_database.dart b/lib/src/database/matrix_sdk_database.dart index 9b46732f..3537c506 100644 --- a/lib/src/database/matrix_sdk_database.dart +++ b/lib/src/database/matrix_sdk_database.dart @@ -1623,6 +1623,25 @@ class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage { return CachedPresence.fromJson(copyMap(rawPresence)); } + @override + Future storeWellKnown(DiscoveryInformation? discoveryInformation) { + if (discoveryInformation == null) { + return _clientBox.delete('discovery_information'); + } + return _clientBox.put( + 'discovery_information', + jsonEncode(discoveryInformation.toJson()), + ); + } + + @override + Future getWellKnown() async { + final rawDiscoveryInformation = + await _clientBox.get('discovery_information'); + if (rawDiscoveryInformation == null) return null; + return DiscoveryInformation.fromJson(jsonDecode(rawDiscoveryInformation)); + } + @override Future delete() async { // database?.path is null on web diff --git a/test/client_test.dart b/test/client_test.dart index 3ec967fb..e9e6dcd7 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -175,6 +175,7 @@ void main() { matrix.userDeviceKeys['@alice:example.com']?.deviceKeys['JLAFKJWSCS'] ?.verified, false); + expect(matrix.wellKnown, isNull); await matrix.handleSync(SyncUpdate.fromJson({ 'next_batch': 'fakesync', @@ -1168,6 +1169,13 @@ void main() { await client.dispose(closeDatabase: true); }); + test('wellKnown cache', () async { + final client = await getClient(); + expect(client.wellKnown, null); + await client.getWellknown(); + expect(client.wellKnown?.mHomeserver.baseUrl.host, 'matrix.example.com'); + }); + test('refreshAccessToken', () async { final client = await getClient(); expect(client.accessToken, 'abcd');