feat: support for authenticated media
This commit is contained in:
parent
987dd750b3
commit
f79096dfbb
|
|
@ -91,6 +91,11 @@ class FakeMatrixApi extends BaseClient {
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<String> servers = {
|
||||||
|
'https://fakeserver.notexisting',
|
||||||
|
'https://fakeserverpriortoauthmedia.notexisting'
|
||||||
|
};
|
||||||
|
|
||||||
FutureOr<Response> mockIntercept(Request request) async {
|
FutureOr<Response> mockIntercept(Request request) async {
|
||||||
// Collect data from Request
|
// Collect data from Request
|
||||||
var action = request.url.path;
|
var action = request.url.path;
|
||||||
|
|
@ -125,8 +130,7 @@ class FakeMatrixApi extends BaseClient {
|
||||||
if (data is Map<String, dynamic> && data['timeout'] is String) {
|
if (data is Map<String, dynamic> && data['timeout'] is String) {
|
||||||
await Future.delayed(Duration(seconds: 5));
|
await Future.delayed(Duration(seconds: 5));
|
||||||
}
|
}
|
||||||
|
if (!servers.contains(request.url.origin)) {
|
||||||
if (request.url.origin != 'https://fakeserver.notexisting') {
|
|
||||||
return Response(
|
return Response(
|
||||||
'<html><head></head><body>Not found...</body></html>', 404);
|
'<html><head></head><body>Not found...</body></html>', 404);
|
||||||
}
|
}
|
||||||
|
|
@ -150,88 +154,105 @@ class FakeMatrixApi extends BaseClient {
|
||||||
|
|
||||||
// Call API
|
// Call API
|
||||||
(_calledEndpoints[action] ??= <dynamic>[]).add(data);
|
(_calledEndpoints[action] ??= <dynamic>[]).add(data);
|
||||||
final act = api[method]?[action];
|
if (request.url.origin ==
|
||||||
if (act != null) {
|
'https://fakeserverpriortoauthmedia.notexisting' &&
|
||||||
res = act(data);
|
action.contains('/client/versions')) {
|
||||||
if (res is Map && res.containsKey('errcode')) {
|
|
||||||
if (res['errcode'] == 'M_NOT_FOUND') {
|
|
||||||
statusCode = 404;
|
|
||||||
} else {
|
|
||||||
statusCode = 405;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (method == 'PUT' && action.contains('/client/v3/sendToDevice/')) {
|
|
||||||
res = {};
|
|
||||||
if (_failToDevice) {
|
|
||||||
statusCode = 500;
|
|
||||||
}
|
|
||||||
} else if (method == 'GET' &&
|
|
||||||
action.contains('/client/v3/rooms/') &&
|
|
||||||
action.contains('/state/m.room.member/') &&
|
|
||||||
!action.endsWith('%40alicyy%3Aexample.com') &&
|
|
||||||
!action.contains('%40getme')) {
|
|
||||||
res = {'displayname': '', 'membership': 'ban'};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
action.contains(
|
|
||||||
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/')) {
|
|
||||||
res = {'event_id': '\$event${_eventCounter++}'};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
action.contains(
|
|
||||||
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
|
||||||
res = {'event_id': '\$event${_eventCounter++}'};
|
|
||||||
} else if (action.contains('/client/v3/sync')) {
|
|
||||||
res = {
|
res = {
|
||||||
'next_batch': DateTime.now().millisecondsSinceEpoch.toString(),
|
'versions': [
|
||||||
|
'r0.0.1',
|
||||||
|
'ra.b.c',
|
||||||
|
'v0.1',
|
||||||
|
'v1.1',
|
||||||
|
'v1.9',
|
||||||
|
'v1.10.1',
|
||||||
|
],
|
||||||
|
'unstable_features': {'m.lazy_load_members': true},
|
||||||
};
|
};
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
_client != null &&
|
|
||||||
action.contains('/account_data/') &&
|
|
||||||
!action.contains('/rooms/')) {
|
|
||||||
final type = Uri.decodeComponent(action.split('/').last);
|
|
||||||
final syncUpdate = sdk.SyncUpdate(
|
|
||||||
nextBatch: '',
|
|
||||||
accountData: [sdk.BasicEvent(content: decodeJson(data), type: type)],
|
|
||||||
);
|
|
||||||
if (_client?.database != null) {
|
|
||||||
await _client?.database?.transaction(() async {
|
|
||||||
await _client?.handleSync(syncUpdate);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await _client?.handleSync(syncUpdate);
|
|
||||||
}
|
|
||||||
res = {};
|
|
||||||
} else if (method == 'PUT' &&
|
|
||||||
_client != null &&
|
|
||||||
action.contains('/account_data/') &&
|
|
||||||
action.contains('/rooms/')) {
|
|
||||||
final segments = action.split('/');
|
|
||||||
final type = Uri.decodeComponent(segments.last);
|
|
||||||
final roomId = Uri.decodeComponent(segments[segments.length - 3]);
|
|
||||||
final syncUpdate = sdk.SyncUpdate(
|
|
||||||
nextBatch: '',
|
|
||||||
rooms: RoomsUpdate(
|
|
||||||
join: {
|
|
||||||
roomId: JoinedRoomUpdate(accountData: [
|
|
||||||
sdk.BasicRoomEvent(
|
|
||||||
content: decodeJson(data), type: type, roomId: roomId)
|
|
||||||
])
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (_client?.database != null) {
|
|
||||||
await _client?.database?.transaction(() async {
|
|
||||||
await _client?.handleSync(syncUpdate);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await _client?.handleSync(syncUpdate);
|
|
||||||
}
|
|
||||||
res = {};
|
|
||||||
} else {
|
} else {
|
||||||
res = {
|
final act = api[method]?[action];
|
||||||
'errcode': 'M_UNRECOGNIZED',
|
if (act != null) {
|
||||||
'error': 'Unrecognized request: $action'
|
res = act(data);
|
||||||
};
|
if (res is Map && res.containsKey('errcode')) {
|
||||||
statusCode = 405;
|
if (res['errcode'] == 'M_NOT_FOUND') {
|
||||||
|
statusCode = 404;
|
||||||
|
} else {
|
||||||
|
statusCode = 405;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
action.contains('/client/v3/sendToDevice/')) {
|
||||||
|
res = {};
|
||||||
|
if (_failToDevice) {
|
||||||
|
statusCode = 500;
|
||||||
|
}
|
||||||
|
} else if (method == 'GET' &&
|
||||||
|
action.contains('/client/v3/rooms/') &&
|
||||||
|
action.contains('/state/m.room.member/') &&
|
||||||
|
!action.endsWith('%40alicyy%3Aexample.com') &&
|
||||||
|
!action.contains('%40getme')) {
|
||||||
|
res = {'displayname': '', 'membership': 'ban'};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
action.contains(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/send/')) {
|
||||||
|
res = {'event_id': '\$event${_eventCounter++}'};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
action.contains(
|
||||||
|
'/client/v3/rooms/!1234%3AfakeServer.notExisting/state/')) {
|
||||||
|
res = {'event_id': '\$event${_eventCounter++}'};
|
||||||
|
} else if (action.contains('/client/v3/sync')) {
|
||||||
|
res = {
|
||||||
|
'next_batch': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||||
|
};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
_client != null &&
|
||||||
|
action.contains('/account_data/') &&
|
||||||
|
!action.contains('/rooms/')) {
|
||||||
|
final type = Uri.decodeComponent(action.split('/').last);
|
||||||
|
final syncUpdate = sdk.SyncUpdate(
|
||||||
|
nextBatch: '',
|
||||||
|
accountData: [sdk.BasicEvent(content: decodeJson(data), type: type)],
|
||||||
|
);
|
||||||
|
if (_client?.database != null) {
|
||||||
|
await _client?.database?.transaction(() async {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
|
}
|
||||||
|
res = {};
|
||||||
|
} else if (method == 'PUT' &&
|
||||||
|
_client != null &&
|
||||||
|
action.contains('/account_data/') &&
|
||||||
|
action.contains('/rooms/')) {
|
||||||
|
final segments = action.split('/');
|
||||||
|
final type = Uri.decodeComponent(segments.last);
|
||||||
|
final roomId = Uri.decodeComponent(segments[segments.length - 3]);
|
||||||
|
final syncUpdate = sdk.SyncUpdate(
|
||||||
|
nextBatch: '',
|
||||||
|
rooms: RoomsUpdate(
|
||||||
|
join: {
|
||||||
|
roomId: JoinedRoomUpdate(accountData: [
|
||||||
|
sdk.BasicRoomEvent(
|
||||||
|
content: decodeJson(data), type: type, roomId: roomId)
|
||||||
|
])
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (_client?.database != null) {
|
||||||
|
await _client?.database?.transaction(() async {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await _client?.handleSync(syncUpdate);
|
||||||
|
}
|
||||||
|
res = {};
|
||||||
|
} else {
|
||||||
|
res = {
|
||||||
|
'errcode': 'M_UNRECOGNIZED',
|
||||||
|
'error': 'Unrecognized request: $action'
|
||||||
|
};
|
||||||
|
statusCode = 405;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unawaited(Future.delayed(Duration(milliseconds: 1)).then((_) async {
|
unawaited(Future.delayed(Duration(milliseconds: 1)).then((_) async {
|
||||||
|
|
@ -1122,7 +1143,19 @@ class FakeMatrixApi extends BaseClient {
|
||||||
'og:image:width': 48,
|
'og:image:width': 48,
|
||||||
'matrix:image:size': 102400
|
'matrix:image:size': 102400
|
||||||
},
|
},
|
||||||
|
'/client/v1/media/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/v3/config': (var req) => {'m.upload.size': 50000000},
|
'/media/v3/config': (var req) => {'m.upload.size': 50000000},
|
||||||
|
'/client/v1/media/config': (var req) => {'m.upload.size': 50000000},
|
||||||
'/.well-known/matrix/client': (var req) => {
|
'/.well-known/matrix/client': (var req) => {
|
||||||
'm.homeserver': {'base_url': 'https://matrix.example.com'},
|
'm.homeserver': {'base_url': 'https://matrix.example.com'},
|
||||||
'm.identity_server': {'base_url': 'https://identity.example.com'},
|
'm.identity_server': {'base_url': 'https://identity.example.com'},
|
||||||
|
|
@ -1690,10 +1723,7 @@ class FakeMatrixApi extends BaseClient {
|
||||||
'/client/v3/rooms/!5345234234%3Aexample.com/messages?from=t_1234a&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
'/client/v3/rooms/!5345234234%3Aexample.com/messages?from=t_1234a&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D':
|
||||||
(var req) => archivesMessageResponse,
|
(var req) => archivesMessageResponse,
|
||||||
'/client/versions': (var req) => {
|
'/client/versions': (var req) => {
|
||||||
'versions': [
|
'versions': ['v1.1', 'v1.2', 'v1.11'],
|
||||||
'v1.1',
|
|
||||||
'v1.2',
|
|
||||||
],
|
|
||||||
'unstable_features': {'m.lazy_load_members': true},
|
'unstable_features': {'m.lazy_load_members': true},
|
||||||
},
|
},
|
||||||
'/client/v3/login': (var req) => {
|
'/client/v3/login': (var req) => {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,14 @@ import 'dart:typed_data';
|
||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:collection/collection.dart' show IterableExtension;
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:olm/olm.dart' as olm;
|
import 'package:olm/olm.dart' as olm;
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
|
|
||||||
import 'package:matrix/encryption.dart';
|
import 'package:matrix/encryption.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
import 'package:matrix/matrix_api_lite/generated/fixed_model.dart';
|
||||||
import 'package:matrix/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart';
|
import 'package:matrix/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart';
|
||||||
import 'package:matrix/src/models/timeline_chunk.dart';
|
import 'package:matrix/src/models/timeline_chunk.dart';
|
||||||
import 'package:matrix/src/utils/cached_stream_controller.dart';
|
import 'package:matrix/src/utils/cached_stream_controller.dart';
|
||||||
|
|
@ -41,6 +43,7 @@ import 'package:matrix/src/utils/run_benchmarked.dart';
|
||||||
import 'package:matrix/src/utils/run_in_root.dart';
|
import 'package:matrix/src/utils/run_in_root.dart';
|
||||||
import 'package:matrix/src/utils/sync_update_item_count.dart';
|
import 'package:matrix/src/utils/sync_update_item_count.dart';
|
||||||
import 'package:matrix/src/utils/try_get_push_rule.dart';
|
import 'package:matrix/src/utils/try_get_push_rule.dart';
|
||||||
|
import 'package:matrix/src/utils/versions_comparator.dart';
|
||||||
|
|
||||||
typedef RoomSorter = int Function(Room a, Room b);
|
typedef RoomSorter = int Function(Room a, Room b);
|
||||||
|
|
||||||
|
|
@ -506,7 +509,7 @@ class Client extends MatrixApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if server supports at least one supported version
|
// Check if server supports at least one supported version
|
||||||
final versions = await getVersions();
|
final versions = _versionsCache = await getVersions();
|
||||||
if (!versions.versions
|
if (!versions.versions
|
||||||
.any((version) => supportedVersions.contains(version))) {
|
.any((version) => supportedVersions.contains(version))) {
|
||||||
throw BadServerVersionsException(
|
throw BadServerVersionsException(
|
||||||
|
|
@ -1158,12 +1161,222 @@ class Client extends MatrixApi {
|
||||||
_archivedRooms.add(ArchivedRoom(room: archivedRoom, timeline: timeline));
|
_archivedRooms.add(ArchivedRoom(room: archivedRoom, timeline: timeline));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetVersionsResponse? _versionsCache;
|
||||||
|
|
||||||
|
Future<bool> authenticatedMediaSupported() async {
|
||||||
|
_versionsCache ??= await getVersions();
|
||||||
|
return _versionsCache?.versions.any(
|
||||||
|
(v) => isVersionGreaterThanOrEqualTo(v, 'v1.11'),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
}
|
||||||
|
|
||||||
final _serverConfigCache = AsyncCache<ServerConfig>(const Duration(hours: 1));
|
final _serverConfigCache = AsyncCache<ServerConfig>(const Duration(hours: 1));
|
||||||
|
|
||||||
/// Gets the config of the content repository, such as upload limit.
|
/// This endpoint allows clients to retrieve the configuration of the content
|
||||||
|
/// repository, such as upload limitations.
|
||||||
|
/// Clients SHOULD use this as a guide when using content repository endpoints.
|
||||||
|
/// All values are intentionally left optional. Clients SHOULD follow
|
||||||
|
/// the advice given in the field description when the field is not available.
|
||||||
|
///
|
||||||
|
/// **NOTE:** Both clients and server administrators should be aware that proxies
|
||||||
|
/// between the client and the server may affect the apparent behaviour of content
|
||||||
|
/// repository APIs, for example, proxies may enforce a lower upload size limit
|
||||||
|
/// than is advertised by the server on this endpoint.
|
||||||
@override
|
@override
|
||||||
Future<ServerConfig> getConfig() =>
|
Future<ServerConfig> getConfig() =>
|
||||||
_serverConfigCache.fetch(() => super.getConfig());
|
_serverConfigCache.fetch(() => _getAuthenticatedConfig());
|
||||||
|
|
||||||
|
// TODO: remove once we are able to autogen this
|
||||||
|
Future<ServerConfig> _getAuthenticatedConfig() async {
|
||||||
|
String path;
|
||||||
|
if (await authenticatedMediaSupported()) {
|
||||||
|
path = '_matrix/client/v1/media/config';
|
||||||
|
} else {
|
||||||
|
path = '_matrix/media/v3/config';
|
||||||
|
}
|
||||||
|
final requestUri = Uri(path: path);
|
||||||
|
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||||
|
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||||
|
final response = await httpClient.send(request);
|
||||||
|
final responseBody = await response.stream.toBytes();
|
||||||
|
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||||
|
final responseString = utf8.decode(responseBody);
|
||||||
|
final json = jsonDecode(responseString);
|
||||||
|
return ServerConfig.fromJson(json as Map<String, Object?>);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [serverName] The server name from the `mxc://` URI (the authoritory component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [mediaId] The media ID from the `mxc://` URI (the path component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed
|
||||||
|
/// remote. This is to prevent routing loops where the server contacts itself. Defaults to
|
||||||
|
/// true if not provided.
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
// TODO: remove once we are able to autogen this
|
||||||
|
Future<FileResponse> getContent(String serverName, String mediaId,
|
||||||
|
{bool? allowRemote}) async {
|
||||||
|
String path;
|
||||||
|
|
||||||
|
if (await authenticatedMediaSupported()) {
|
||||||
|
path =
|
||||||
|
'_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}';
|
||||||
|
} else {
|
||||||
|
path =
|
||||||
|
'_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}';
|
||||||
|
}
|
||||||
|
final requestUri = Uri(path: path, queryParameters: {
|
||||||
|
if (allowRemote != null && !await authenticatedMediaSupported())
|
||||||
|
// removed with msc3916, so just to be explicit
|
||||||
|
'allow_remote': allowRemote.toString(),
|
||||||
|
});
|
||||||
|
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||||
|
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||||
|
final response = await httpClient.send(request);
|
||||||
|
final responseBody = await response.stream.toBytes();
|
||||||
|
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||||
|
return FileResponse(
|
||||||
|
contentType: response.headers['content-type'], data: responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This will download content from the content repository (same as
|
||||||
|
/// the previous endpoint) but replace the target file name with the one
|
||||||
|
/// provided by the caller.
|
||||||
|
///
|
||||||
|
/// [serverName] The server name from the `mxc://` URI (the authoritory component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [mediaId] The media ID from the `mxc://` URI (the path component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [fileName] A filename to give in the `Content-Disposition` header.
|
||||||
|
///
|
||||||
|
/// [allowRemote] Indicates to the server that it should not attempt to fetch the media if it is deemed
|
||||||
|
/// remote. This is to prevent routing loops where the server contacts itself. Defaults to
|
||||||
|
/// true if not provided.
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
// TODO: remove once we are able to autogen this
|
||||||
|
Future<FileResponse> getContentOverrideName(
|
||||||
|
String serverName, String mediaId, String fileName,
|
||||||
|
{bool? allowRemote}) async {
|
||||||
|
String path;
|
||||||
|
if (await authenticatedMediaSupported()) {
|
||||||
|
path =
|
||||||
|
'_matrix/client/v1/media/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}';
|
||||||
|
} else {
|
||||||
|
path =
|
||||||
|
'_matrix/media/v3/download/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}/${Uri.encodeComponent(fileName)}';
|
||||||
|
}
|
||||||
|
final requestUri = Uri(path: path, queryParameters: {
|
||||||
|
if (allowRemote != null && !await authenticatedMediaSupported())
|
||||||
|
// removed with msc3916, so just to be explicit
|
||||||
|
'allow_remote': allowRemote.toString(),
|
||||||
|
});
|
||||||
|
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||||
|
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||||
|
final response = await httpClient.send(request);
|
||||||
|
final responseBody = await response.stream.toBytes();
|
||||||
|
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||||
|
return FileResponse(
|
||||||
|
contentType: response.headers['content-type'], data: responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// **Note:**
|
||||||
|
/// Clients should consider avoiding this endpoint for URLs posted in encrypted
|
||||||
|
/// rooms. Encrypted rooms often contain more sensitive information the users
|
||||||
|
/// do not want to share with the homeserver, and this can mean that the URLs
|
||||||
|
/// being shared should also not be shared with the homeserver.
|
||||||
|
///
|
||||||
|
/// [url] The URL to get a preview of.
|
||||||
|
///
|
||||||
|
/// [ts] The preferred point in time to return a preview for. The server may
|
||||||
|
/// return a newer version if it does not have the requested version
|
||||||
|
/// available.
|
||||||
|
@override
|
||||||
|
// TODO: remove once we are able to autogen this
|
||||||
|
Future<GetUrlPreviewResponse> getUrlPreview(Uri url, {int? ts}) async {
|
||||||
|
String path;
|
||||||
|
if (await authenticatedMediaSupported()) {
|
||||||
|
path = '_matrix/client/v1/media/preview_url';
|
||||||
|
} else {
|
||||||
|
path = '_matrix/media/v3/preview_url';
|
||||||
|
}
|
||||||
|
final requestUri = Uri(path: path, queryParameters: {
|
||||||
|
'url': url.toString(),
|
||||||
|
if (ts != null) 'ts': ts.toString(),
|
||||||
|
});
|
||||||
|
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||||
|
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||||
|
final response = await httpClient.send(request);
|
||||||
|
final responseBody = await response.stream.toBytes();
|
||||||
|
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||||
|
final responseString = utf8.decode(responseBody);
|
||||||
|
final json = jsonDecode(responseString);
|
||||||
|
return GetUrlPreviewResponse.fromJson(json as Map<String, Object?>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Download a thumbnail of content from the content repository.
|
||||||
|
/// See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails) section for more information.
|
||||||
|
///
|
||||||
|
/// [serverName] The server name from the `mxc://` URI (the authoritory component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [mediaId] The media ID from the `mxc://` URI (the path component)
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// [width] The *desired* width of the thumbnail. The actual thumbnail may be
|
||||||
|
/// larger than the size specified.
|
||||||
|
///
|
||||||
|
/// [height] The *desired* height of the thumbnail. The actual thumbnail may be
|
||||||
|
/// larger than the size specified.
|
||||||
|
///
|
||||||
|
/// [method] The desired resizing method. See the [Thumbnails](https://spec.matrix.org/unstable/client-server-api/#thumbnails)
|
||||||
|
/// section for more information.
|
||||||
|
///
|
||||||
|
/// [allowRemote] Indicates to the server that it should not attempt to fetch
|
||||||
|
/// the media if it is deemed remote. This is to prevent routing loops
|
||||||
|
/// where the server contacts itself. Defaults to true if not provided.
|
||||||
|
@override
|
||||||
|
// TODO: remove once we are able to autogen this
|
||||||
|
Future<FileResponse> getContentThumbnail(
|
||||||
|
String serverName, String mediaId, int width, int height,
|
||||||
|
{Method? method, bool? allowRemote}) async {
|
||||||
|
String path;
|
||||||
|
if (await authenticatedMediaSupported()) {
|
||||||
|
path =
|
||||||
|
'_matrix/client/v1/media/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}';
|
||||||
|
} else {
|
||||||
|
path =
|
||||||
|
'_matrix/media/v3/thumbnail/${Uri.encodeComponent(serverName)}/${Uri.encodeComponent(mediaId)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
final requestUri = Uri(path: path, queryParameters: {
|
||||||
|
'width': width.toString(),
|
||||||
|
'height': height.toString(),
|
||||||
|
if (method != null) 'method': method.name,
|
||||||
|
if (allowRemote != null && !await authenticatedMediaSupported())
|
||||||
|
// removed with msc3916, so just to be explicit
|
||||||
|
'allow_remote': allowRemote.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
final request = Request('GET', baseUri!.resolveUri(requestUri));
|
||||||
|
request.headers['authorization'] = 'Bearer ${bearerToken!}';
|
||||||
|
final response = await httpClient.send(request);
|
||||||
|
final responseBody = await response.stream.toBytes();
|
||||||
|
if (response.statusCode != 200) unexpectedResponse(response, responseBody);
|
||||||
|
return FileResponse(
|
||||||
|
contentType: response.headers['content-type'], data: responseBody);
|
||||||
|
}
|
||||||
|
|
||||||
/// Uploads a file and automatically caches it in the database, if it is small enough
|
/// Uploads a file and automatically caches it in the database, if it is small enough
|
||||||
/// and returns the mxc url.
|
/// and returns the mxc url.
|
||||||
|
|
|
||||||
|
|
@ -544,6 +544,66 @@ class Event extends MatrixEvent {
|
||||||
/// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k
|
/// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k
|
||||||
/// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment.
|
/// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment.
|
||||||
/// [animated] says weather the thumbnail is animated
|
/// [animated] says weather the thumbnail is animated
|
||||||
|
///
|
||||||
|
/// Throws an exception if the scheme is not `mxc` or the homeserver is not
|
||||||
|
/// set.
|
||||||
|
///
|
||||||
|
/// Important! To use this link you have to set a http header like this:
|
||||||
|
/// `headers: {"authorization": "Bearer ${client.accessToken}"}`
|
||||||
|
Future<Uri?> getAttachmentUri(
|
||||||
|
{bool getThumbnail = false,
|
||||||
|
bool useThumbnailMxcUrl = false,
|
||||||
|
double width = 800.0,
|
||||||
|
double height = 800.0,
|
||||||
|
ThumbnailMethod method = ThumbnailMethod.scale,
|
||||||
|
int minNoThumbSize = _minNoThumbSize,
|
||||||
|
bool animated = false}) async {
|
||||||
|
if (![EventTypes.Message, EventTypes.Sticker].contains(type) ||
|
||||||
|
!hasAttachment ||
|
||||||
|
isAttachmentEncrypted) {
|
||||||
|
return null; // can't url-thumbnail in encrypted rooms
|
||||||
|
}
|
||||||
|
if (useThumbnailMxcUrl && !hasThumbnail) {
|
||||||
|
return null; // can't fetch from thumbnail
|
||||||
|
}
|
||||||
|
final thisInfoMap = useThumbnailMxcUrl ? thumbnailInfoMap : infoMap;
|
||||||
|
final thisMxcUrl =
|
||||||
|
useThumbnailMxcUrl ? infoMap['thumbnail_url'] : content['url'];
|
||||||
|
// if we have as method scale, we can return safely the original image, should it be small enough
|
||||||
|
if (getThumbnail &&
|
||||||
|
method == ThumbnailMethod.scale &&
|
||||||
|
thisInfoMap['size'] is int &&
|
||||||
|
thisInfoMap['size'] < minNoThumbSize) {
|
||||||
|
getThumbnail = false;
|
||||||
|
}
|
||||||
|
// now generate the actual URLs
|
||||||
|
if (getThumbnail) {
|
||||||
|
return await Uri.parse(thisMxcUrl).getThumbnailUri(
|
||||||
|
room.client,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
method: method,
|
||||||
|
animated: animated,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await Uri.parse(thisMxcUrl).getDownloadUri(room.client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the attachment https URL to display in the timeline, taking into account if the original image is tiny.
|
||||||
|
/// Returns null for encrypted rooms, if the image can't be fetched via http url or if the event does not contain an attachment.
|
||||||
|
/// Set [getThumbnail] to true to fetch the thumbnail, set [width], [height] and [method]
|
||||||
|
/// for the respective thumbnailing properties.
|
||||||
|
/// [minNoThumbSize] is the minimum size that an original image may be to not fetch its thumbnail, defaults to 80k
|
||||||
|
/// [useThumbnailMxcUrl] says weather to use the mxc url of the thumbnail, rather than the original attachment.
|
||||||
|
/// [animated] says weather the thumbnail is animated
|
||||||
|
///
|
||||||
|
/// Throws an exception if the scheme is not `mxc` or the homeserver is not
|
||||||
|
/// set.
|
||||||
|
///
|
||||||
|
/// Important! To use this link you have to set a http header like this:
|
||||||
|
/// `headers: {"authorization": "Bearer ${client.accessToken}"}`
|
||||||
|
@Deprecated('Use getAttachmentUri() instead')
|
||||||
Uri? getAttachmentUrl(
|
Uri? getAttachmentUrl(
|
||||||
{bool getThumbnail = false,
|
{bool getThumbnail = false,
|
||||||
bool useThumbnailMxcUrl = false,
|
bool useThumbnailMxcUrl = false,
|
||||||
|
|
@ -655,9 +715,13 @@ class Event extends MatrixEvent {
|
||||||
final canDownloadFileFromServer = uint8list == null && !fromLocalStoreOnly;
|
final canDownloadFileFromServer = uint8list == null && !fromLocalStoreOnly;
|
||||||
if (canDownloadFileFromServer) {
|
if (canDownloadFileFromServer) {
|
||||||
final httpClient = room.client.httpClient;
|
final httpClient = room.client.httpClient;
|
||||||
downloadCallback ??=
|
downloadCallback ??= (Uri url) async => (await httpClient.get(
|
||||||
(Uri url) async => (await httpClient.get(url)).bodyBytes;
|
url,
|
||||||
uint8list = await downloadCallback(mxcUrl.getDownloadLink(room.client));
|
headers: {'authorization': 'Bearer ${room.client.accessToken}'},
|
||||||
|
))
|
||||||
|
.bodyBytes;
|
||||||
|
uint8list =
|
||||||
|
await downloadCallback(await mxcUrl.getDownloadUri(room.client));
|
||||||
storeable = database != null &&
|
storeable = database != null &&
|
||||||
storeable &&
|
storeable &&
|
||||||
uint8list.lengthInBytes < database.maxFileSize;
|
uint8list.lengthInBytes < database.maxFileSize;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,79 @@ import 'dart:core';
|
||||||
import 'package:matrix/src/client.dart';
|
import 'package:matrix/src/client.dart';
|
||||||
|
|
||||||
extension MxcUriExtension on Uri {
|
extension MxcUriExtension on Uri {
|
||||||
/// Returns a download Link to this content.
|
/// Transforms this `mxc://` Uri into a `http` resource, which can be used
|
||||||
|
/// to download the content.
|
||||||
|
///
|
||||||
|
/// Throws an exception if the scheme is not `mxc` or the homeserver is not
|
||||||
|
/// set.
|
||||||
|
///
|
||||||
|
/// Important! To use this link you have to set a http header like this:
|
||||||
|
/// `headers: {"authorization": "Bearer ${client.accessToken}"}`
|
||||||
|
Future<Uri> getDownloadUri(Client client) async {
|
||||||
|
String uriPath;
|
||||||
|
|
||||||
|
if (await client.authenticatedMediaSupported()) {
|
||||||
|
uriPath =
|
||||||
|
'_matrix/client/v1/media/download/$host${hasPort ? ':$port' : ''}$path';
|
||||||
|
} else {
|
||||||
|
uriPath =
|
||||||
|
'_matrix/media/v3/download/$host${hasPort ? ':$port' : ''}$path';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isScheme('mxc')
|
||||||
|
? client.homeserver != null
|
||||||
|
? client.homeserver?.resolve(uriPath) ?? Uri()
|
||||||
|
: Uri()
|
||||||
|
: Uri();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms this `mxc://` Uri into a `http` resource, which can be used
|
||||||
|
/// to download the content with the given `width` and
|
||||||
|
/// `height`. `method` can be `ThumbnailMethod.crop` or
|
||||||
|
/// `ThumbnailMethod.scale` and defaults to `ThumbnailMethod.scale`.
|
||||||
|
/// If `animated` (default false) is set to true, an animated thumbnail is requested
|
||||||
|
/// as per MSC2705. Thumbnails only animate if the media repository supports that.
|
||||||
|
///
|
||||||
|
/// Throws an exception if the scheme is not `mxc` or the homeserver is not
|
||||||
|
/// set.
|
||||||
|
///
|
||||||
|
/// Important! To use this link you have to set a http header like this:
|
||||||
|
/// `headers: {"authorization": "Bearer ${client.accessToken}"}`
|
||||||
|
Future<Uri> getThumbnailUri(Client client,
|
||||||
|
{num? width,
|
||||||
|
num? height,
|
||||||
|
ThumbnailMethod? method = ThumbnailMethod.crop,
|
||||||
|
bool? animated = false}) async {
|
||||||
|
if (!isScheme('mxc')) return Uri();
|
||||||
|
final homeserver = client.homeserver;
|
||||||
|
if (homeserver == null) {
|
||||||
|
return Uri();
|
||||||
|
}
|
||||||
|
|
||||||
|
String requestPath;
|
||||||
|
if (await client.authenticatedMediaSupported()) {
|
||||||
|
requestPath =
|
||||||
|
'/_matrix/client/v1/media/thumbnail/$host${hasPort ? ':$port' : ''}$path';
|
||||||
|
} else {
|
||||||
|
requestPath =
|
||||||
|
'/_matrix/media/v3/thumbnail/$host${hasPort ? ':$port' : ''}$path';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uri(
|
||||||
|
scheme: homeserver.scheme,
|
||||||
|
host: homeserver.host,
|
||||||
|
path: requestPath,
|
||||||
|
port: homeserver.port,
|
||||||
|
queryParameters: {
|
||||||
|
if (width != null) 'width': width.round().toString(),
|
||||||
|
if (height != null) 'height': height.round().toString(),
|
||||||
|
if (method != null) 'method': method.toString().split('.').last,
|
||||||
|
if (animated != null) 'animated': animated.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Use `getDownloadUri()` instead')
|
||||||
Uri getDownloadLink(Client matrix) => isScheme('mxc')
|
Uri getDownloadLink(Client matrix) => isScheme('mxc')
|
||||||
? matrix.homeserver != null
|
? matrix.homeserver != null
|
||||||
? matrix.homeserver?.resolve(
|
? matrix.homeserver?.resolve(
|
||||||
|
|
@ -35,6 +107,7 @@ extension MxcUriExtension on Uri {
|
||||||
/// `ThumbnailMethod.scale` and defaults to `ThumbnailMethod.scale`.
|
/// `ThumbnailMethod.scale` and defaults to `ThumbnailMethod.scale`.
|
||||||
/// If `animated` (default false) is set to true, an animated thumbnail is requested
|
/// If `animated` (default false) is set to true, an animated thumbnail is requested
|
||||||
/// as per MSC2705. Thumbnails only animate if the media repository supports that.
|
/// as per MSC2705. Thumbnails only animate if the media repository supports that.
|
||||||
|
@Deprecated('Use `getThumbnailUri()` instead')
|
||||||
Uri getThumbnail(Client matrix,
|
Uri getThumbnail(Client matrix,
|
||||||
{num? width,
|
{num? width,
|
||||||
num? height,
|
num? height,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||||
|
|
||||||
|
bool isVersionGreaterThanOrEqualTo(String version, String target) {
|
||||||
|
try {
|
||||||
|
final versionParts =
|
||||||
|
version.substring(1).split('.').map(int.parse).toList();
|
||||||
|
final targetParts = target.substring(1).split('.').map(int.parse).toList();
|
||||||
|
|
||||||
|
for (int i = 0; i < versionParts.length; i++) {
|
||||||
|
if (i >= targetParts.length) return true; // reached the end, both equal
|
||||||
|
if (versionParts[i] > targetParts[i]) return true; // ver greater
|
||||||
|
if (versionParts[i] < targetParts[i]) return false; // tar greater
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (e, s) {
|
||||||
|
Logs().e(
|
||||||
|
'[_isVersionGreaterThanOrEqualTo] Failed to parse version $version',
|
||||||
|
e,
|
||||||
|
s,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1310,8 +1310,8 @@ void main() {
|
||||||
final THUMBNAIL_BUFF = Uint8List.fromList([2]);
|
final THUMBNAIL_BUFF = Uint8List.fromList([2]);
|
||||||
Future<Uint8List> downloadCallback(Uri uri) async {
|
Future<Uint8List> downloadCallback(Uri uri) async {
|
||||||
return {
|
return {
|
||||||
'/_matrix/media/v3/download/example.org/file': FILE_BUFF,
|
'/_matrix/client/v1/media/download/example.org/file': FILE_BUFF,
|
||||||
'/_matrix/media/v3/download/example.org/thumb': THUMBNAIL_BUFF,
|
'/_matrix/client/v1/media/download/example.org/thumb': THUMBNAIL_BUFF,
|
||||||
}[uri.path]!;
|
}[uri.path]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1366,22 +1366,23 @@ void main() {
|
||||||
'mxc://example.org/file');
|
'mxc://example.org/file');
|
||||||
expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true).toString(),
|
expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true).toString(),
|
||||||
'mxc://example.org/thumb');
|
'mxc://example.org/thumb');
|
||||||
expect(event.getAttachmentUrl().toString(),
|
expect((await event.getAttachmentUri()).toString(),
|
||||||
'https://fakeserver.notexisting/_matrix/media/v3/download/example.org/file');
|
'https://fakeserver.notexisting/_matrix/client/v1/media/download/example.org/file');
|
||||||
expect(event.getAttachmentUrl(getThumbnail: true).toString(),
|
expect((await event.getAttachmentUri(getThumbnail: true)).toString(),
|
||||||
'https://fakeserver.notexisting/_matrix/media/v3/thumbnail/example.org/file?width=800&height=800&method=scale&animated=false');
|
'https://fakeserver.notexisting/_matrix/client/v1/media/thumbnail/example.org/file?width=800&height=800&method=scale&animated=false');
|
||||||
expect(event.getAttachmentUrl(useThumbnailMxcUrl: true).toString(),
|
|
||||||
'https://fakeserver.notexisting/_matrix/media/v3/download/example.org/thumb');
|
|
||||||
expect(
|
expect(
|
||||||
event
|
(await event.getAttachmentUri(useThumbnailMxcUrl: true)).toString(),
|
||||||
.getAttachmentUrl(getThumbnail: true, useThumbnailMxcUrl: true)
|
'https://fakeserver.notexisting/_matrix/client/v1/media/download/example.org/thumb');
|
||||||
.toString(),
|
|
||||||
'https://fakeserver.notexisting/_matrix/media/v3/thumbnail/example.org/thumb?width=800&height=800&method=scale&animated=false');
|
|
||||||
expect(
|
expect(
|
||||||
event
|
(await event.getAttachmentUri(
|
||||||
.getAttachmentUrl(getThumbnail: true, minNoThumbSize: 9000000)
|
getThumbnail: true, useThumbnailMxcUrl: true))
|
||||||
.toString(),
|
.toString(),
|
||||||
'https://fakeserver.notexisting/_matrix/media/v3/download/example.org/file');
|
'https://fakeserver.notexisting/_matrix/client/v1/media/thumbnail/example.org/thumb?width=800&height=800&method=scale&animated=false');
|
||||||
|
expect(
|
||||||
|
(await event.getAttachmentUri(
|
||||||
|
getThumbnail: true, minNoThumbSize: 9000000))
|
||||||
|
.toString(),
|
||||||
|
'https://fakeserver.notexisting/_matrix/client/v1/media/download/example.org/file');
|
||||||
|
|
||||||
buffer = await event.downloadAndDecryptAttachment(
|
buffer = await event.downloadAndDecryptAttachment(
|
||||||
downloadCallback: downloadCallback);
|
downloadCallback: downloadCallback);
|
||||||
|
|
@ -1404,8 +1405,9 @@ void main() {
|
||||||
Uint8List.fromList([0x74, 0x68, 0x75, 0x6D, 0x62, 0x0A]);
|
Uint8List.fromList([0x74, 0x68, 0x75, 0x6D, 0x62, 0x0A]);
|
||||||
Future<Uint8List> downloadCallback(Uri uri) async {
|
Future<Uint8List> downloadCallback(Uri uri) async {
|
||||||
return {
|
return {
|
||||||
'/_matrix/media/v3/download/example.com/file': FILE_BUFF_ENC,
|
'/_matrix/client/v1/media/download/example.com/file': FILE_BUFF_ENC,
|
||||||
'/_matrix/media/v3/download/example.com/thumb': THUMB_BUFF_ENC,
|
'/_matrix/client/v1/media/download/example.com/thumb':
|
||||||
|
THUMB_BUFF_ENC,
|
||||||
}[uri.path]!;
|
}[uri.path]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1508,7 +1510,7 @@ void main() {
|
||||||
Future<Uint8List> downloadCallback(Uri uri) async {
|
Future<Uint8List> downloadCallback(Uri uri) async {
|
||||||
serverHits++;
|
serverHits++;
|
||||||
return {
|
return {
|
||||||
'/_matrix/media/v3/download/example.org/newfile': FILE_BUFF,
|
'/_matrix/client/v1/media/download/example.org/newfile': FILE_BUFF,
|
||||||
}[uri.path]!;
|
}[uri.path]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1550,7 +1552,7 @@ void main() {
|
||||||
Future<Uint8List> downloadCallback(Uri uri) async {
|
Future<Uint8List> downloadCallback(Uri uri) async {
|
||||||
serverHits++;
|
serverHits++;
|
||||||
return {
|
return {
|
||||||
'/_matrix/media/v3/download/example.org/newfile': FILE_BUFF,
|
'/_matrix/client/v1/media/download/example.org/newfile': FILE_BUFF,
|
||||||
}[uri.path]!;
|
}[uri.path]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1599,7 +1601,7 @@ void main() {
|
||||||
Future<Uint8List> downloadCallback(Uri uri) async {
|
Future<Uint8List> downloadCallback(Uri uri) async {
|
||||||
serverHits++;
|
serverHits++;
|
||||||
return {
|
return {
|
||||||
'/_matrix/media/v3/download/example.org/newfile': FILE_BUFF,
|
'/_matrix/client/v1/media/download/example.org/newfile': FILE_BUFF,
|
||||||
}[uri.path]!;
|
}[uri.path]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,19 +32,20 @@ void main() {
|
||||||
final content = Uri.parse(mxc);
|
final content = Uri.parse(mxc);
|
||||||
expect(content.isScheme('mxc'), true);
|
expect(content.isScheme('mxc'), true);
|
||||||
|
|
||||||
expect(content.getDownloadLink(client).toString(),
|
expect((await content.getDownloadUri(client)).toString(),
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/download/exampleserver.abc/abcdefghijklmn');
|
'${client.homeserver.toString()}/_matrix/client/v1/media/download/exampleserver.abc/abcdefghijklmn');
|
||||||
expect(content.getThumbnail(client, width: 50, height: 50).toString(),
|
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
|
||||||
expect(
|
expect(
|
||||||
content
|
(await content.getThumbnailUri(client, width: 50, height: 50))
|
||||||
.getThumbnail(client,
|
.toString(),
|
||||||
|
'${client.homeserver.toString()}/_matrix/client/v1/media/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
||||||
|
expect(
|
||||||
|
(await content.getThumbnailUri(client,
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
method: ThumbnailMethod.scale,
|
method: ThumbnailMethod.scale,
|
||||||
animated: true)
|
animated: true))
|
||||||
.toString(),
|
.toString(),
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale&animated=true');
|
'${client.homeserver.toString()}/_matrix/client/v1/media/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale&animated=true');
|
||||||
});
|
});
|
||||||
test('other port', () async {
|
test('other port', () async {
|
||||||
final client = Client('testclient', httpClient: FakeMatrixApi());
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
|
|
@ -55,19 +56,20 @@ void main() {
|
||||||
final content = Uri.parse(mxc);
|
final content = Uri.parse(mxc);
|
||||||
expect(content.isScheme('mxc'), true);
|
expect(content.isScheme('mxc'), true);
|
||||||
|
|
||||||
expect(content.getDownloadLink(client).toString(),
|
expect((await content.getDownloadUri(client)).toString(),
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/download/exampleserver.abc/abcdefghijklmn');
|
'${client.homeserver.toString()}/_matrix/client/v1/media/download/exampleserver.abc/abcdefghijklmn');
|
||||||
expect(content.getThumbnail(client, width: 50, height: 50).toString(),
|
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
|
||||||
expect(
|
expect(
|
||||||
content
|
(await content.getThumbnailUri(client, width: 50, height: 50))
|
||||||
.getThumbnail(client,
|
.toString(),
|
||||||
|
'${client.homeserver.toString()}/_matrix/client/v1/media/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
||||||
|
expect(
|
||||||
|
(await content.getThumbnailUri(client,
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
method: ThumbnailMethod.scale,
|
method: ThumbnailMethod.scale,
|
||||||
animated: true)
|
animated: true))
|
||||||
.toString(),
|
.toString(),
|
||||||
'https://fakeserver.notexisting:1337/_matrix/media/v3/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale&animated=true');
|
'https://fakeserver.notexisting:1337/_matrix/client/v1/media/thumbnail/exampleserver.abc/abcdefghijklmn?width=50&height=50&method=scale&animated=true');
|
||||||
});
|
});
|
||||||
test('other remote port', () async {
|
test('other remote port', () async {
|
||||||
final client = Client('testclient', httpClient: FakeMatrixApi());
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
|
|
@ -77,18 +79,39 @@ void main() {
|
||||||
final content = Uri.parse(mxc);
|
final content = Uri.parse(mxc);
|
||||||
expect(content.isScheme('mxc'), true);
|
expect(content.isScheme('mxc'), true);
|
||||||
|
|
||||||
expect(content.getDownloadLink(client).toString(),
|
expect((await content.getDownloadUri(client)).toString(),
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/download/exampleserver.abc:1234/abcdefghijklmn');
|
'${client.homeserver.toString()}/_matrix/client/v1/media/download/exampleserver.abc:1234/abcdefghijklmn');
|
||||||
expect(content.getThumbnail(client, width: 50, height: 50).toString(),
|
expect(
|
||||||
'${client.homeserver.toString()}/_matrix/media/v3/thumbnail/exampleserver.abc:1234/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
(await content.getThumbnailUri(client, width: 50, height: 50))
|
||||||
|
.toString(),
|
||||||
|
'${client.homeserver.toString()}/_matrix/client/v1/media/thumbnail/exampleserver.abc:1234/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
||||||
});
|
});
|
||||||
test('Wrong scheme returns empty object', () async {
|
test('Wrong scheme throw exception', () async {
|
||||||
final client = Client('testclient', httpClient: FakeMatrixApi());
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
await client.checkHomeserver(Uri.parse('https://fakeserver.notexisting'),
|
||||||
checkWellKnown: false);
|
checkWellKnown: false);
|
||||||
final mxc = Uri.parse('https://wrong-scheme.com');
|
final mxc = Uri.parse('https://wrong-scheme.com');
|
||||||
expect(mxc.getDownloadLink(client).toString(), '');
|
expect((await mxc.getDownloadUri(client)).toString(), '');
|
||||||
expect(mxc.getThumbnail(client).toString(), '');
|
expect((await mxc.getThumbnailUri(client)).toString(), '');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auth media fallback', () async {
|
||||||
|
final client = Client('testclient', httpClient: FakeMatrixApi());
|
||||||
|
await client.checkHomeserver(
|
||||||
|
Uri.parse('https://fakeserverpriortoauthmedia.notexisting'),
|
||||||
|
checkWellKnown: false);
|
||||||
|
|
||||||
|
expect(await client.authenticatedMediaSupported(), false);
|
||||||
|
final mxc = 'mxc://exampleserver.abc:1234/abcdefghijklmn';
|
||||||
|
final content = Uri.parse(mxc);
|
||||||
|
expect(content.isScheme('mxc'), true);
|
||||||
|
|
||||||
|
expect((await content.getDownloadUri(client)).toString(),
|
||||||
|
'${client.homeserver.toString()}/_matrix/media/v3/download/exampleserver.abc:1234/abcdefghijklmn');
|
||||||
|
expect(
|
||||||
|
(await content.getThumbnailUri(client, width: 50, height: 50))
|
||||||
|
.toString(),
|
||||||
|
'${client.homeserver.toString()}/_matrix/media/v3/thumbnail/exampleserver.abc:1234/abcdefghijklmn?width=50&height=50&method=crop&animated=false');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue