[Connection] Throw MatrixErrors
This commit is contained in:
parent
ccdb33ba69
commit
4c6c3715d9
|
@ -24,12 +24,12 @@
|
||||||
library famedlysdk;
|
library famedlysdk;
|
||||||
|
|
||||||
export 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
export 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
||||||
export 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
|
||||||
export 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
export 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
||||||
export 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
export 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||||
export 'package:famedlysdk/src/sync/EventUpdate.dart';
|
export 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
export 'package:famedlysdk/src/sync/UserUpdate.dart';
|
export 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||||
export 'package:famedlysdk/src/utils/ChatTime.dart';
|
export 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
|
export 'package:famedlysdk/src/utils/MatrixException.dart';
|
||||||
export 'package:famedlysdk/src/utils/MatrixFile.dart';
|
export 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
export 'package:famedlysdk/src/utils/MxContent.dart';
|
export 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
export 'package:famedlysdk/src/utils/StatesMap.dart';
|
export 'package:famedlysdk/src/utils/StatesMap.dart';
|
||||||
|
|
|
@ -38,7 +38,6 @@ import 'RoomList.dart';
|
||||||
import 'RoomState.dart';
|
import 'RoomState.dart';
|
||||||
import 'User.dart';
|
import 'User.dart';
|
||||||
import 'requests/SetPushersRequest.dart';
|
import 'requests/SetPushersRequest.dart';
|
||||||
import 'responses/ErrorResponse.dart';
|
|
||||||
import 'responses/PushrulesResponse.dart';
|
import 'responses/PushrulesResponse.dart';
|
||||||
import 'utils/Profile.dart';
|
import 'utils/Profile.dart';
|
||||||
|
|
||||||
|
@ -156,28 +155,19 @@ class Client {
|
||||||
/// Checks the supported versions of the Matrix protocol and the supported
|
/// Checks the supported versions of the Matrix protocol and the supported
|
||||||
/// login types. Returns false if the server is not compatible with the
|
/// login types. Returns false if the server is not compatible with the
|
||||||
/// client. Automatically sets [matrixVersions] and [lazyLoadMembers].
|
/// client. Automatically sets [matrixVersions] and [lazyLoadMembers].
|
||||||
|
/// Throws FormatException, TimeoutException and MatrixException on error.
|
||||||
Future<bool> checkServer(serverUrl) async {
|
Future<bool> checkServer(serverUrl) async {
|
||||||
|
try {
|
||||||
homeserver = serverUrl;
|
homeserver = serverUrl;
|
||||||
|
|
||||||
final versionResp = await connection.jsonRequest(
|
final versionResp = await connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/versions");
|
type: HTTPType.GET, action: "/client/versions");
|
||||||
if (versionResp is ErrorResponse) {
|
|
||||||
connection.onError.add(ErrorResponse(errcode: "NO_RESPONSE", error: ""));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> versions = List<String>.from(versionResp["versions"]);
|
final List<String> versions = List<String>.from(versionResp["versions"]);
|
||||||
|
|
||||||
if (versions == null) {
|
|
||||||
connection.onError.add(ErrorResponse(errcode: "NO_RESPONSE", error: ""));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < versions.length; i++) {
|
for (int i = 0; i < versions.length; i++) {
|
||||||
if (versions[i] == "r0.5.0")
|
if (versions[i] == "r0.5.0")
|
||||||
break;
|
break;
|
||||||
else if (i == versions.length - 1) {
|
else if (i == versions.length - 1) {
|
||||||
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,17 +176,14 @@ class Client {
|
||||||
|
|
||||||
if (versionResp.containsKey("unstable_features") &&
|
if (versionResp.containsKey("unstable_features") &&
|
||||||
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
|
versionResp["unstable_features"].containsKey("m.lazy_load_members")) {
|
||||||
lazyLoadMembers = versionResp["unstable_features"]["m.lazy_load_members"]
|
lazyLoadMembers = versionResp["unstable_features"]
|
||||||
|
["m.lazy_load_members"]
|
||||||
? true
|
? true
|
||||||
: false;
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loginResp = await connection.jsonRequest(
|
final loginResp = await connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/login");
|
type: HTTPType.GET, action: "/client/r0/login");
|
||||||
if (loginResp is ErrorResponse) {
|
|
||||||
connection.onError.add(loginResp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<dynamic> flows = loginResp["flows"];
|
final List<dynamic> flows = loginResp["flows"];
|
||||||
|
|
||||||
|
@ -205,16 +192,19 @@ class Client {
|
||||||
flows[i]["type"] == "m.login.password")
|
flows[i]["type"] == "m.login.password")
|
||||||
break;
|
break;
|
||||||
else if (i == flows.length - 1) {
|
else if (i == flows.length - 1) {
|
||||||
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
this.homeserver = this.matrixVersions = null;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the login and allows the client to call all APIs which require
|
/// Handles the login and allows the client to call all APIs which require
|
||||||
/// authentication. Returns false if the login was not successful.
|
/// authentication. Returns false if the login was not successful. Throws
|
||||||
|
/// MatrixException if login was not successful.
|
||||||
Future<bool> login(String username, String password) async {
|
Future<bool> login(String username, String password) async {
|
||||||
final loginResp = await connection
|
final loginResp = await connection
|
||||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||||
|
@ -228,15 +218,10 @@ class Client {
|
||||||
"initial_device_display_name": "Famedly Talk"
|
"initial_device_display_name": "Famedly Talk"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loginResp is ErrorResponse) {
|
|
||||||
connection.onError.add(loginResp);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final userID = loginResp["user_id"];
|
final userID = loginResp["user_id"];
|
||||||
final accessToken = loginResp["access_token"];
|
final accessToken = loginResp["access_token"];
|
||||||
if (userID == null || accessToken == null) {
|
if (userID == null || accessToken == null) {
|
||||||
connection.onError.add(ErrorResponse(errcode: "NO_SUPPORT", error: ""));
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await connection.connect(
|
await connection.connect(
|
||||||
|
@ -253,12 +238,15 @@ class Client {
|
||||||
/// Sends a logout command to the homeserver and clears all local data,
|
/// Sends a logout command to the homeserver and clears all local data,
|
||||||
/// including all persistent data from the store.
|
/// including all persistent data from the store.
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
try {
|
||||||
|
await connection.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/logout");
|
type: HTTPType.POST, action: "/client/r0/logout");
|
||||||
if (resp is ErrorResponse) connection.onError.add(resp);
|
} catch (exception) {
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
await connection.clear();
|
await connection.clear();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the combined profile information for this user. This API may be used to
|
/// 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
|
/// fetch the user's own profile information or other users; either locally
|
||||||
|
@ -266,10 +254,6 @@ class Client {
|
||||||
Future<Profile> getProfileFromUserId(String userId) async {
|
Future<Profile> getProfileFromUserId(String userId) async {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
final dynamic resp = await connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/profile/${userId}");
|
type: HTTPType.GET, action: "/client/r0/profile/${userId}");
|
||||||
if (resp is ErrorResponse) {
|
|
||||||
connection.onError.add(resp);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return Profile.fromJson(resp);
|
return Profile.fromJson(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,8 +279,7 @@ class Client {
|
||||||
String action = "/client/r0/sync?filter=$syncFilters&timeout=0";
|
String action = "/client/r0/sync?filter=$syncFilters&timeout=0";
|
||||||
final sync =
|
final sync =
|
||||||
await connection.jsonRequest(type: HTTPType.GET, action: action);
|
await connection.jsonRequest(type: HTTPType.GET, action: action);
|
||||||
if (!(sync is ErrorResponse) &&
|
if (sync["rooms"]["leave"] is Map<String, dynamic>) {
|
||||||
sync["rooms"]["leave"] is Map<String, dynamic>) {
|
|
||||||
for (var entry in sync["rooms"]["leave"].entries) {
|
for (var entry in sync["rooms"]["leave"].entries) {
|
||||||
final String id = entry.key;
|
final String id = entry.key;
|
||||||
final dynamic room = entry.value;
|
final dynamic room = entry.value;
|
||||||
|
@ -377,6 +360,7 @@ class Client {
|
||||||
if (params == null && invite != null)
|
if (params == null && invite != null)
|
||||||
for (int i = 0; i < invite.length; i++) inviteIDs.add(invite[i].id);
|
for (int i = 0; i < invite.length; i++) inviteIDs.add(invite[i].id);
|
||||||
|
|
||||||
|
try {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
final dynamic resp = await connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/createRoom",
|
action: "/client/r0/createRoom",
|
||||||
|
@ -385,25 +369,20 @@ class Client {
|
||||||
"invite": inviteIDs,
|
"invite": inviteIDs,
|
||||||
}
|
}
|
||||||
: params);
|
: params);
|
||||||
|
|
||||||
if (resp is ErrorResponse) {
|
|
||||||
connection.onError.add(resp);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp["room_id"];
|
return resp["room_id"];
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads a new user avatar for this user. Returns ErrorResponse if something went wrong.
|
/// Uploads a new user avatar for this user.
|
||||||
Future<dynamic> setAvatar(MatrixFile file) async {
|
Future<void> setAvatar(MatrixFile file) async {
|
||||||
final uploadResp = await connection.upload(file);
|
final uploadResp = await connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return uploadResp;
|
await connection.jsonRequest(
|
||||||
final setAvatarResp = await connection.jsonRequest(
|
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/profile/$userID/avatar_url",
|
action: "/client/r0/profile/$userID/avatar_url",
|
||||||
data: {"avatar_url": uploadResp});
|
data: {"avatar_url": uploadResp});
|
||||||
if (setAvatarResp is ErrorResponse) return setAvatarResp;
|
return;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the pushrules for the logged in user.
|
/// Fetches the pushrules for the logged in user.
|
||||||
|
@ -414,26 +393,16 @@ class Client {
|
||||||
action: "/client/r0/pushrules/",
|
action: "/client/r0/pushrules/",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resp is ErrorResponse) {
|
|
||||||
connection.onError.add(resp);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PushrulesResponse.fromJson(resp);
|
return PushrulesResponse.fromJson(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This endpoint allows the creation, modification and deletion of pushers for this user ID.
|
/// This endpoint allows the creation, modification and deletion of pushers for this user ID.
|
||||||
Future<dynamic> setPushers(SetPushersRequest data) async {
|
Future<void> setPushers(SetPushersRequest data) async {
|
||||||
final dynamic resp = await connection.jsonRequest(
|
await connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/pushers/set",
|
action: "/client/r0/pushers/set",
|
||||||
data: data.toJson(),
|
data: data.toJson(),
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
if (resp is ErrorResponse) {
|
|
||||||
connection.onError.add(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,10 @@ import 'package:mime_type/mime_type.dart';
|
||||||
|
|
||||||
import 'Client.dart';
|
import 'Client.dart';
|
||||||
import 'User.dart';
|
import 'User.dart';
|
||||||
import 'responses/ErrorResponse.dart';
|
|
||||||
import 'sync/EventUpdate.dart';
|
import 'sync/EventUpdate.dart';
|
||||||
import 'sync/RoomUpdate.dart';
|
import 'sync/RoomUpdate.dart';
|
||||||
import 'sync/UserUpdate.dart';
|
import 'sync/UserUpdate.dart';
|
||||||
|
import 'utils/MatrixException.dart';
|
||||||
|
|
||||||
enum HTTPType { GET, POST, PUT, DELETE }
|
enum HTTPType { GET, POST, PUT, DELETE }
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class Connection {
|
||||||
new StreamController.broadcast();
|
new StreamController.broadcast();
|
||||||
|
|
||||||
/// Synchronization erros are coming here.
|
/// Synchronization erros are coming here.
|
||||||
final StreamController<ErrorResponse> onError =
|
final StreamController<MatrixException> onError =
|
||||||
new StreamController.broadcast();
|
new StreamController.broadcast();
|
||||||
|
|
||||||
/// This is called once, when the first sync has received.
|
/// This is called once, when the first sync has received.
|
||||||
|
@ -177,6 +177,8 @@ class Connection {
|
||||||
|
|
||||||
/// Used for all Matrix json requests using the [c2s API](https://matrix.org/docs/spec/client_server/r0.4.0.html).
|
/// 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
|
/// 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
|
/// this! For example to send a message to a Matrix room with the id
|
||||||
/// '!fjd823j:example.com' you call:
|
/// '!fjd823j:example.com' you call:
|
||||||
|
@ -192,7 +194,7 @@ class Connection {
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
Future<dynamic> jsonRequest(
|
Future<Map<String, dynamic>> jsonRequest(
|
||||||
{HTTPType type,
|
{HTTPType type,
|
||||||
String action,
|
String action,
|
||||||
dynamic data = "",
|
dynamic data = "",
|
||||||
|
@ -219,6 +221,7 @@ class Connection {
|
||||||
"[REQUEST ${type.toString().split('.').last}] Action: $action, Data: $data");
|
"[REQUEST ${type.toString().split('.').last}] Action: $action, Data: $data");
|
||||||
|
|
||||||
http.Response resp;
|
http.Response resp;
|
||||||
|
Map<String, dynamic> jsonResp = {};
|
||||||
try {
|
try {
|
||||||
switch (type.toString().split('.').last) {
|
switch (type.toString().split('.').last) {
|
||||||
case "GET":
|
case "GET":
|
||||||
|
@ -242,52 +245,47 @@ class Connection {
|
||||||
.timeout(Duration(seconds: timeout));
|
.timeout(Duration(seconds: timeout));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} on TimeoutException catch (_) {
|
jsonResp = jsonDecode(resp.body)
|
||||||
return ErrorResponse(
|
as Map<String, dynamic>; // May throw FormatException
|
||||||
error: "No connection possible...",
|
|
||||||
errcode: "TIMEOUT",
|
if (jsonResp.containsKey("errcode") && jsonResp["errcode"] is String) {
|
||||||
request: resp?.request);
|
// The server has responsed with an matrix related error.
|
||||||
} catch (e) {
|
MatrixException exception = MatrixException(resp);
|
||||||
return ErrorResponse(
|
if (exception.error == MatrixError.M_UNKNOWN_TOKEN) {
|
||||||
error: "No connection possible...",
|
// The token is no longer valid. Need to sign off....
|
||||||
errcode: "NO_CONNECTION",
|
onError.add(exception);
|
||||||
request: resp?.request);
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> jsonResp;
|
throw exception;
|
||||||
try {
|
|
||||||
jsonResp = jsonDecode(resp.body) as Map<String, dynamic>;
|
|
||||||
} catch (e) {
|
|
||||||
return ErrorResponse(
|
|
||||||
error: "No connection possible...",
|
|
||||||
errcode: "MALFORMED",
|
|
||||||
request: resp?.request);
|
|
||||||
}
|
|
||||||
if (jsonResp.containsKey("errcode") && jsonResp["errcode"] is String) {
|
|
||||||
if (jsonResp["errcode"] == "M_UNKNOWN_TOKEN") clear();
|
|
||||||
return ErrorResponse.fromJson(jsonResp, resp?.request);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.debug) print("[RESPONSE] ${jsonResp.toString()}");
|
if (client.debug) print("[RESPONSE] ${jsonResp.toString()}");
|
||||||
|
} on ArgumentError catch (exception) {
|
||||||
|
print(exception);
|
||||||
|
// Ignore this error
|
||||||
|
} catch (_) {
|
||||||
|
print(_);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
return jsonResp;
|
return jsonResp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads a file with the name [fileName] as base64 encoded to the server
|
/// Uploads a file with the name [fileName] as base64 encoded to the server
|
||||||
/// and returns the mxc url as a string or an [ErrorResponse].
|
/// and returns the mxc url as a string.
|
||||||
Future<dynamic> upload(MatrixFile file) async {
|
Future<String> upload(MatrixFile file) async {
|
||||||
dynamic fileBytes;
|
dynamic fileBytes;
|
||||||
if (client.homeserver != "https://fakeServer.notExisting")
|
if (client.homeserver != "https://fakeServer.notExisting")
|
||||||
fileBytes = file.bytes;
|
fileBytes = file.bytes;
|
||||||
String fileName = file.path.split("/").last.toLowerCase();
|
String fileName = file.path.split("/").last.toLowerCase();
|
||||||
String mimeType = mime(file.path);
|
String mimeType = mime(file.path);
|
||||||
print("[UPLOADING] $fileName, type: $mimeType, size: ${fileBytes?.length}");
|
print("[UPLOADING] $fileName, type: $mimeType, size: ${fileBytes?.length}");
|
||||||
final dynamic resp = await jsonRequest(
|
final Map<String, dynamic> resp = await jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/media/r0/upload?filename=$fileName",
|
action: "/media/r0/upload?filename=$fileName",
|
||||||
data: fileBytes,
|
data: fileBytes,
|
||||||
contentType: mimeType);
|
contentType: mimeType);
|
||||||
if (resp is ErrorResponse) return resp;
|
|
||||||
return resp["content_uri"];
|
return resp["content_uri"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,15 +300,11 @@ class Connection {
|
||||||
action += "&timeout=30000";
|
action += "&timeout=30000";
|
||||||
action += "&since=${client.prevBatch}";
|
action += "&since=${client.prevBatch}";
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
_syncRequest = jsonRequest(type: HTTPType.GET, action: action);
|
_syncRequest = jsonRequest(type: HTTPType.GET, action: action);
|
||||||
final int hash = _syncRequest.hashCode;
|
final int hash = _syncRequest.hashCode;
|
||||||
final syncResp = await _syncRequest;
|
final syncResp = await _syncRequest;
|
||||||
if (hash != _syncRequest.hashCode) return;
|
if (hash != _syncRequest.hashCode) return;
|
||||||
if (syncResp is ErrorResponse) {
|
|
||||||
//onError.add(syncResp);
|
|
||||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), () {});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (client.store != null)
|
if (client.store != null)
|
||||||
await client.store.transaction(() {
|
await client.store.transaction(() {
|
||||||
handleSync(syncResp);
|
handleSync(syncResp);
|
||||||
|
@ -321,13 +315,13 @@ class Connection {
|
||||||
await handleSync(syncResp);
|
await handleSync(syncResp);
|
||||||
if (client.prevBatch == null) client.connection.onFirstSync.add(true);
|
if (client.prevBatch == null) client.connection.onFirstSync.add(true);
|
||||||
client.prevBatch = syncResp["next_batch"];
|
client.prevBatch = syncResp["next_batch"];
|
||||||
} catch (e) {
|
|
||||||
onError
|
|
||||||
.add(ErrorResponse(errcode: "CRITICAL_ERROR", error: e.toString()));
|
|
||||||
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), () {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hash == _syncRequest.hashCode) _sync();
|
if (hash == _syncRequest.hashCode) _sync();
|
||||||
|
} on MatrixException catch (exception) {
|
||||||
|
onError.add(exception);
|
||||||
|
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||||
|
} catch (exception) {
|
||||||
|
await Future.delayed(Duration(seconds: syncErrorTimeoutSec), _sync);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSync(dynamic sync) {
|
void handleSync(dynamic sync) {
|
||||||
|
|
|
@ -25,10 +25,10 @@ import 'package:famedlysdk/src/Client.dart';
|
||||||
import 'package:famedlysdk/src/Event.dart';
|
import 'package:famedlysdk/src/Event.dart';
|
||||||
import 'package:famedlysdk/src/RoomAccountData.dart';
|
import 'package:famedlysdk/src/RoomAccountData.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/MatrixException.dart';
|
||||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
//import 'package:image/image.dart';
|
//import 'package:image/image.dart';
|
||||||
|
@ -248,35 +248,33 @@ class Room {
|
||||||
return ChatTime.now();
|
return ChatTime.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to change the name of this room.
|
/// Call the Matrix API to change the name of this room. Returns the event ID of the
|
||||||
Future<dynamic> setName(String newName) async {
|
/// new m.room.name event.
|
||||||
dynamic res = await client.connection.jsonRequest(
|
Future<String> setName(String newName) async {
|
||||||
|
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/state/m.room.name",
|
action: "/client/r0/rooms/${id}/state/m.room.name",
|
||||||
data: {"name": newName});
|
data: {"name": newName});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return resp["event_id"];
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to change the topic of this room.
|
/// Call the Matrix API to change the topic of this room.
|
||||||
Future<dynamic> setDescription(String newName) async {
|
Future<String> setDescription(String newName) async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/state/m.room.topic",
|
action: "/client/r0/rooms/${id}/state/m.room.topic",
|
||||||
data: {"topic": newName});
|
data: {"topic": newName});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return resp["event_id"];
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> _sendRawEventNow(Map<String, dynamic> content,
|
Future<String> _sendRawEventNow(Map<String, dynamic> content,
|
||||||
{String txid = null}) async {
|
{String txid = null}) async {
|
||||||
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
|
if (txid == null) txid = "txid${DateTime.now().millisecondsSinceEpoch}";
|
||||||
final dynamic res = await client.connection.jsonRequest(
|
final Map<String, dynamic> res = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
|
action: "/client/r0/rooms/${id}/send/m.room.message/$txid",
|
||||||
data: content);
|
data: content);
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return res["event_id"];
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> sendTextEvent(String message, {String txid = null}) =>
|
Future<String> sendTextEvent(String message, {String txid = null}) =>
|
||||||
|
@ -291,8 +289,7 @@ class Room {
|
||||||
if (msgType == "m.video") return sendAudioEvent(file);
|
if (msgType == "m.video") return sendAudioEvent(file);
|
||||||
String fileName = file.path.split("/").last;
|
String fileName = file.path.split("/").last;
|
||||||
|
|
||||||
final dynamic uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return null;
|
|
||||||
|
|
||||||
// Send event
|
// Send event
|
||||||
Map<String, dynamic> content = {
|
Map<String, dynamic> content = {
|
||||||
|
@ -311,8 +308,7 @@ class Room {
|
||||||
Future<String> sendAudioEvent(MatrixFile file,
|
Future<String> sendAudioEvent(MatrixFile file,
|
||||||
{String txid = null, int width, int height}) async {
|
{String txid = null, int width, int height}) async {
|
||||||
String fileName = file.path.split("/").last;
|
String fileName = file.path.split("/").last;
|
||||||
final dynamic uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return null;
|
|
||||||
Map<String, dynamic> content = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.audio",
|
"msgtype": "m.audio",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -329,8 +325,7 @@ class Room {
|
||||||
Future<String> sendImageEvent(MatrixFile file,
|
Future<String> sendImageEvent(MatrixFile file,
|
||||||
{String txid = null, int width, int height}) async {
|
{String txid = null, int width, int height}) async {
|
||||||
String fileName = file.path.split("/").last;
|
String fileName = file.path.split("/").last;
|
||||||
final dynamic uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return null;
|
|
||||||
Map<String, dynamic> content = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.image",
|
"msgtype": "m.image",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -354,8 +349,7 @@ class Room {
|
||||||
int thumbnailWidth,
|
int thumbnailWidth,
|
||||||
int thumbnailHeight}) async {
|
int thumbnailHeight}) async {
|
||||||
String fileName = file.path.split("/").last;
|
String fileName = file.path.split("/").last;
|
||||||
final dynamic uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return null;
|
|
||||||
Map<String, dynamic> content = {
|
Map<String, dynamic> content = {
|
||||||
"msgtype": "m.video",
|
"msgtype": "m.video",
|
||||||
"body": fileName,
|
"body": fileName,
|
||||||
|
@ -376,8 +370,7 @@ class Room {
|
||||||
}
|
}
|
||||||
if (thumbnail != null) {
|
if (thumbnail != null) {
|
||||||
String thumbnailName = file.path.split("/").last;
|
String thumbnailName = file.path.split("/").last;
|
||||||
final dynamic thumbnailUploadResp = await client.connection.upload(file);
|
final String thumbnailUploadResp = await client.connection.upload(file);
|
||||||
if (thumbnailUploadResp is ErrorResponse) return null;
|
|
||||||
content["info"]["thumbnail_url"] = thumbnailUploadResp;
|
content["info"]["thumbnail_url"] = thumbnailUploadResp;
|
||||||
content["info"]["thumbnail_info"] = {
|
content["info"]["thumbnail_info"] = {
|
||||||
"size": thumbnail.size,
|
"size": thumbnail.size,
|
||||||
|
@ -422,9 +415,18 @@ class Room {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send the text and on success, store and display a *sent* event.
|
// Send the text and on success, store and display a *sent* event.
|
||||||
final dynamic res = await _sendRawEventNow(content, txid: messageID);
|
try {
|
||||||
|
final String res = await _sendRawEventNow(content, txid: messageID);
|
||||||
if (res is ErrorResponse || !(res["event_id"] is String)) {
|
eventUpdate.content["status"] = 1;
|
||||||
|
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||||
|
eventUpdate.content["event_id"] = res;
|
||||||
|
client.connection.onEvent.add(eventUpdate);
|
||||||
|
await client.store?.transaction(() {
|
||||||
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
} catch (exception) {
|
||||||
// On error, set status to -1
|
// On error, set status to -1
|
||||||
eventUpdate.content["status"] = -1;
|
eventUpdate.content["status"] = -1;
|
||||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
||||||
|
@ -433,16 +435,6 @@ class Room {
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
client.store.storeEventUpdate(eventUpdate);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
eventUpdate.content["status"] = 1;
|
|
||||||
eventUpdate.content["unsigned"] = {"transaction_id": messageID};
|
|
||||||
eventUpdate.content["event_id"] = res["event_id"];
|
|
||||||
client.connection.onEvent.add(eventUpdate);
|
|
||||||
await client.store?.transaction(() {
|
|
||||||
client.store.storeEventUpdate(eventUpdate);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
return res["event_id"];
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -450,12 +442,16 @@ class Room {
|
||||||
/// Call the Matrix API to join this room if the user is not already a member.
|
/// Call the Matrix API to join this room if the user is not already a member.
|
||||||
/// If this room is intended to be a direct chat, the direct chat flag will
|
/// If this room is intended to be a direct chat, the direct chat flag will
|
||||||
/// automatically be set.
|
/// automatically be set.
|
||||||
Future<dynamic> join() async {
|
Future<void> join() async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
try {
|
||||||
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/join");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/join");
|
||||||
if (res is ErrorResponse) {
|
if (states.containsKey(client.userID) &&
|
||||||
client.connection.onError.add(res);
|
states[client.userID].content["is_direct"] is bool &&
|
||||||
if (res.error == "No known servers") {
|
states[client.userID].content["is_direct"])
|
||||||
|
addToDirectChat(states[client.userID].sender.id);
|
||||||
|
} on MatrixException catch (exception) {
|
||||||
|
if (exception.errorMessage == "No known servers") {
|
||||||
client.store?.forgetRoom(id);
|
client.store?.forgetRoom(id);
|
||||||
client.connection.onRoomUpdate.add(
|
client.connection.onRoomUpdate.add(
|
||||||
RoomUpdate(
|
RoomUpdate(
|
||||||
|
@ -465,88 +461,78 @@ class Room {
|
||||||
highlight_count: 0),
|
highlight_count: 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res;
|
rethrow;
|
||||||
}
|
}
|
||||||
if (states.containsKey(client.userID) &&
|
|
||||||
states[client.userID].content["is_direct"] is bool &&
|
|
||||||
states[client.userID].content["is_direct"])
|
|
||||||
addToDirectChat(states[client.userID].sender.id);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to leave this room. If this room is set as a direct
|
/// Call the Matrix API to leave this room. If this room is set as a direct
|
||||||
/// chat, this will be removed too.
|
/// chat, this will be removed too.
|
||||||
Future<dynamic> leave() async {
|
Future<void> leave() async {
|
||||||
if (directChatMatrixID != "") await removeFromDirectChat();
|
if (directChatMatrixID != "") await removeFromDirectChat();
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/leave");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/leave");
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to forget this room if you already left it.
|
/// Call the Matrix API to forget this room if you already left it.
|
||||||
Future<dynamic> forget() async {
|
Future<void> forget() async {
|
||||||
client.store.forgetRoom(id);
|
client.store.forgetRoom(id);
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST, action: "/client/r0/rooms/${id}/forget");
|
type: HTTPType.POST, action: "/client/r0/rooms/${id}/forget");
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to kick a user from this room.
|
/// Call the Matrix API to kick a user from this room.
|
||||||
Future<dynamic> kick(String userID) async {
|
Future<void> kick(String userID) async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/kick",
|
action: "/client/r0/rooms/${id}/kick",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to ban a user from this room.
|
/// Call the Matrix API to ban a user from this room.
|
||||||
Future<dynamic> ban(String userID) async {
|
Future<void> ban(String userID) async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/ban",
|
action: "/client/r0/rooms/${id}/ban",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to unban a banned user from this room.
|
/// Call the Matrix API to unban a banned user from this room.
|
||||||
Future<dynamic> unban(String userID) async {
|
Future<void> unban(String userID) async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/unban",
|
action: "/client/r0/rooms/${id}/unban",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the power level of the user with the [userID] to the value [power].
|
/// Set the power level of the user with the [userID] to the value [power].
|
||||||
Future<dynamic> setPower(String userID, int power) async {
|
/// 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<String> setPower(String userID, int power) async {
|
||||||
if (states["m.room.power_levels"] == null) return null;
|
if (states["m.room.power_levels"] == null) return null;
|
||||||
Map<String, dynamic> powerMap = {}
|
Map<String, dynamic> powerMap = {}
|
||||||
..addAll(states["m.room.power_levels"].content);
|
..addAll(states["m.room.power_levels"].content);
|
||||||
if (powerMap["users"] == null) powerMap["users"] = {};
|
if (powerMap["users"] == null) powerMap["users"] = {};
|
||||||
powerMap["users"][userID] = power;
|
powerMap["users"][userID] = power;
|
||||||
|
|
||||||
dynamic res = await client.connection.jsonRequest(
|
final Map<String, dynamic> resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.power_levels",
|
action: "/client/r0/rooms/$id/state/m.room.power_levels",
|
||||||
data: powerMap);
|
data: powerMap);
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return resp["event_id"];
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call the Matrix API to invite a user to this room.
|
/// Call the Matrix API to invite a user to this room.
|
||||||
Future<dynamic> invite(String userID) async {
|
Future<void> invite(String userID) async {
|
||||||
dynamic res = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/${id}/invite",
|
action: "/client/r0/rooms/${id}/invite",
|
||||||
data: {"user_id": userID});
|
data: {"user_id": userID});
|
||||||
if (res is ErrorResponse) client.connection.onError.add(res);
|
return;
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request more previous events from the server. [historyCount] defines how much events should
|
/// Request more previous events from the server. [historyCount] defines how much events should
|
||||||
|
@ -559,8 +545,6 @@ class Room {
|
||||||
action:
|
action:
|
||||||
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Connection.syncFilters}");
|
"/client/r0/rooms/$id/messages?from=${prev_batch}&dir=b&limit=$historyCount&filter=${Connection.syncFilters}");
|
||||||
|
|
||||||
if (resp is ErrorResponse) return;
|
|
||||||
|
|
||||||
if (onHistoryReceived != null) onHistoryReceived();
|
if (onHistoryReceived != null) onHistoryReceived();
|
||||||
prev_batch = resp["end"];
|
prev_batch = resp["end"];
|
||||||
client.store?.storeRoomPrevBatch(this);
|
client.store?.storeRoomPrevBatch(this);
|
||||||
|
@ -634,51 +618,51 @@ class Room {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets this room as a direct chat for this user.
|
/// Sets this room as a direct chat for this user if not already.
|
||||||
Future<dynamic> addToDirectChat(String userID) async {
|
Future<void> addToDirectChat(String userID) async {
|
||||||
Map<String, dynamic> directChats = client.directChats;
|
Map<String, dynamic> directChats = client.directChats;
|
||||||
if (directChats.containsKey(userID)) if (!directChats[userID].contains(id))
|
if (directChats.containsKey(userID)) if (!directChats[userID].contains(id))
|
||||||
directChats[userID].add(id);
|
directChats[userID].add(id);
|
||||||
else
|
else
|
||||||
return null; // Is already in direct chats
|
return; // Is already in direct chats
|
||||||
else
|
else
|
||||||
directChats[userID] = [id];
|
directChats[userID] = [id];
|
||||||
|
|
||||||
final resp = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||||
data: directChats);
|
data: directChats);
|
||||||
return resp;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets this room as a direct chat for this user.
|
/// Removes this room from all direct chat tags.
|
||||||
Future<dynamic> removeFromDirectChat() async {
|
Future<void> removeFromDirectChat() async {
|
||||||
Map<String, dynamic> directChats = client.directChats;
|
Map<String, dynamic> directChats = client.directChats;
|
||||||
if (directChats.containsKey(directChatMatrixID) &&
|
if (directChats.containsKey(directChatMatrixID) &&
|
||||||
directChats[directChatMatrixID].contains(id))
|
directChats[directChatMatrixID].contains(id))
|
||||||
directChats[directChatMatrixID].remove(id);
|
directChats[directChatMatrixID].remove(id);
|
||||||
else
|
else
|
||||||
return null; // Nothing to do here
|
return; // Nothing to do here
|
||||||
|
|
||||||
final resp = await client.connection.jsonRequest(
|
await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
action: "/client/r0/user/${client.userID}/account_data/m.direct",
|
||||||
data: directChats);
|
data: directChats);
|
||||||
return resp;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends *m.fully_read* and *m.read* for the given event ID.
|
/// Sends *m.fully_read* and *m.read* for the given event ID.
|
||||||
Future<dynamic> sendReadReceipt(String eventID) async {
|
Future<void> sendReadReceipt(String eventID) async {
|
||||||
this.notificationCount = 0;
|
this.notificationCount = 0;
|
||||||
client?.store?.resetNotificationCount(this.id);
|
client?.store?.resetNotificationCount(this.id);
|
||||||
final dynamic resp = client.connection.jsonRequest(
|
client.connection.jsonRequest(
|
||||||
type: HTTPType.POST,
|
type: HTTPType.POST,
|
||||||
action: "/client/r0/rooms/$id/read_markers",
|
action: "/client/r0/rooms/$id/read_markers",
|
||||||
data: {
|
data: {
|
||||||
"m.fully_read": eventID,
|
"m.fully_read": eventID,
|
||||||
"m.read": eventID,
|
"m.read": eventID,
|
||||||
});
|
});
|
||||||
return resp;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Room from a json String which comes normally from the store. If the
|
/// Returns a Room from a json String which comes normally from the store. If the
|
||||||
|
@ -770,8 +754,6 @@ class Room {
|
||||||
|
|
||||||
dynamic res = await client.connection.jsonRequest(
|
dynamic res = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/rooms/${id}/members");
|
type: HTTPType.GET, action: "/client/r0/rooms/${id}/members");
|
||||||
if (res is ErrorResponse || !(res["chunk"] is List<dynamic>))
|
|
||||||
return participants;
|
|
||||||
|
|
||||||
for (num i = 0; i < res["chunk"].length; i++) {
|
for (num i = 0; i < res["chunk"].length; i++) {
|
||||||
User newUser = RoomState.fromJson(res["chunk"][i], this).asUser;
|
User newUser = RoomState.fromJson(res["chunk"][i], this).asUser;
|
||||||
|
@ -794,7 +776,9 @@ class Room {
|
||||||
if (states[mxID] != null)
|
if (states[mxID] != null)
|
||||||
return states[mxID].asUser;
|
return states[mxID].asUser;
|
||||||
else {
|
else {
|
||||||
|
try {
|
||||||
requestUser(mxID);
|
requestUser(mxID);
|
||||||
|
} catch (_) {}
|
||||||
return User(mxID, room: this);
|
return User(mxID, room: this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -805,12 +789,14 @@ class Room {
|
||||||
/// lazy loading.
|
/// lazy loading.
|
||||||
Future<User> requestUser(String mxID) async {
|
Future<User> requestUser(String mxID) async {
|
||||||
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
if (mxID == null || !_requestingMatrixIds.add(mxID)) return null;
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
Map<String, dynamic> resp;
|
||||||
|
try {
|
||||||
|
resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET,
|
type: HTTPType.GET,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
action: "/client/r0/rooms/$id/state/m.room.member/$mxID");
|
||||||
if (resp is ErrorResponse) {
|
} catch (exception) {
|
||||||
_requestingMatrixIds.remove(mxID);
|
_requestingMatrixIds.remove(mxID);
|
||||||
return null;
|
rethrow;
|
||||||
}
|
}
|
||||||
final User user = User(mxID,
|
final User user = User(mxID,
|
||||||
displayName: resp["displayname"],
|
displayName: resp["displayname"],
|
||||||
|
@ -837,7 +823,6 @@ class Room {
|
||||||
Future<Event> getEventById(String eventID) async {
|
Future<Event> getEventById(String eventID) async {
|
||||||
final dynamic resp = await client.connection.jsonRequest(
|
final dynamic resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
type: HTTPType.GET, action: "/client/r0/rooms/$id/event/$eventID");
|
||||||
if (resp is ErrorResponse) return null;
|
|
||||||
return Event.fromJson(resp, this);
|
return Event.fromJson(resp, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,16 +850,15 @@ class Room {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uploads a new user avatar for this room. Returns ErrorResponse if something went wrong
|
/// Uploads a new user avatar for this room. Returns the event ID of the new
|
||||||
/// and the event ID otherwise.
|
/// m.room.avatar event.
|
||||||
Future<dynamic> setAvatar(MatrixFile file) async {
|
Future<String> setAvatar(MatrixFile file) async {
|
||||||
final uploadResp = await client.connection.upload(file);
|
final String uploadResp = await client.connection.upload(file);
|
||||||
if (uploadResp is ErrorResponse) return uploadResp;
|
final Map<String, dynamic> setAvatarResp = await client.connection
|
||||||
final setAvatarResp = await client.connection.jsonRequest(
|
.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
action: "/client/r0/rooms/$id/state/m.room.avatar/",
|
||||||
data: {"url": uploadResp});
|
data: {"url": uploadResp});
|
||||||
if (setAvatarResp is ErrorResponse) return setAvatarResp;
|
|
||||||
return setAvatarResp["event_id"];
|
return setAvatarResp["event_id"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -979,7 +963,6 @@ class Room {
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/override/$id",
|
action: "/client/r0/pushrules/global/override/$id",
|
||||||
data: {});
|
data: {});
|
||||||
if (resp == ErrorResponse) return resp;
|
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
|
@ -1001,7 +984,6 @@ class Room {
|
||||||
type: HTTPType.DELETE,
|
type: HTTPType.DELETE,
|
||||||
action: "/client/r0/pushrules/global/room/$id",
|
action: "/client/r0/pushrules/global/room/$id",
|
||||||
data: {});
|
data: {});
|
||||||
if (resp == ErrorResponse) return resp;
|
|
||||||
}
|
}
|
||||||
resp = await client.connection.jsonRequest(
|
resp = await client.connection.jsonRequest(
|
||||||
type: HTTPType.PUT,
|
type: HTTPType.PUT,
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
import 'package:famedlysdk/famedlysdk.dart';
|
import 'package:famedlysdk/famedlysdk.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/RoomState.dart';
|
import 'package:famedlysdk/src/RoomState.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
import 'package:famedlysdk/src/utils/MxContent.dart';
|
import 'package:famedlysdk/src/utils/MxContent.dart';
|
||||||
|
|
||||||
|
@ -113,28 +112,16 @@ class User extends RoomState {
|
||||||
: displayName;
|
: displayName;
|
||||||
|
|
||||||
/// Call the Matrix API to kick this user from this room.
|
/// Call the Matrix API to kick this user from this room.
|
||||||
Future<dynamic> kick() async {
|
Future<void> kick() => room.kick(id);
|
||||||
dynamic res = await room.kick(id);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the Matrix API to ban this user from this room.
|
/// Call the Matrix API to ban this user from this room.
|
||||||
Future<dynamic> ban() async {
|
Future<void> ban() => room.ban(id);
|
||||||
dynamic res = await room.ban(id);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the Matrix API to unban this banned user from this room.
|
/// Call the Matrix API to unban this banned user from this room.
|
||||||
Future<dynamic> unban() async {
|
Future<void> unban() => room.unban(id);
|
||||||
dynamic res = await room.unban(id);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the Matrix API to change the power level of this user.
|
/// Call the Matrix API to change the power level of this user.
|
||||||
Future<dynamic> setPower(int power) async {
|
Future<void> setPower(int power) => room.setPower(id, power);
|
||||||
dynamic res = await room.setPower(id, power);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an existing direct chat ID with this user or creates a new one.
|
/// Returns an existing direct chat ID with this user or creates a new one.
|
||||||
/// Returns null on error.
|
/// Returns null on error.
|
||||||
|
@ -153,11 +140,6 @@ class User extends RoomState {
|
||||||
"preset": "trusted_private_chat"
|
"preset": "trusted_private_chat"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp is ErrorResponse) {
|
|
||||||
room.client.connection.onError.add(resp);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String newRoomID = resp["room_id"];
|
final String newRoomID = resp["room_id"];
|
||||||
|
|
||||||
if (newRoomID == null) return newRoomID;
|
if (newRoomID == null) return newRoomID;
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2019 Zender & Kurtz GbR.
|
|
||||||
*
|
|
||||||
* Authors:
|
|
||||||
* Christian Pauly <krille@famedly.com>
|
|
||||||
* Marcel Radzio <mtrnord@famedly.com>
|
|
||||||
*
|
|
||||||
* This file is part of famedlysdk.
|
|
||||||
*
|
|
||||||
* famedlysdk is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* famedlysdk is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
/// Represents a special response from the Homeserver for errors.
|
|
||||||
class ErrorResponse {
|
|
||||||
/// The unique identifier for this error.
|
|
||||||
String errcode;
|
|
||||||
|
|
||||||
/// A human readable error description.
|
|
||||||
String error;
|
|
||||||
|
|
||||||
/// The frozen request which triggered this Error
|
|
||||||
http.Request request;
|
|
||||||
|
|
||||||
ErrorResponse({this.errcode, this.error, this.request});
|
|
||||||
|
|
||||||
ErrorResponse.fromJson(Map<String, dynamic> json, http.Request newRequest) {
|
|
||||||
errcode = json['errcode'];
|
|
||||||
error = json['error'] ?? "";
|
|
||||||
request = newRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
|
||||||
data['errcode'] = this.errcode;
|
|
||||||
data['error'] = this.error;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
105
lib/src/utils/MatrixException.dart
Normal file
105
lib/src/utils/MatrixException.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019 Zender & Kurtz GbR.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* Christian Pauly <krille@famedly.com>
|
||||||
|
* Marcel Radzio <mtrnord@famedly.com>
|
||||||
|
*
|
||||||
|
* This file is part of famedlysdk.
|
||||||
|
*
|
||||||
|
* famedlysdk is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* famedlysdk is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with famedlysdk. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
enum MatrixError {
|
||||||
|
M_UNKNOWN,
|
||||||
|
M_UNKNOWN_TOKEN,
|
||||||
|
M_NOT_FOUND,
|
||||||
|
M_FORBIDDEN,
|
||||||
|
M_LIMIT_EXCEEDED,
|
||||||
|
M_USER_IN_USE,
|
||||||
|
M_THREEPID_IN_USE,
|
||||||
|
M_THREEPID_DENIED,
|
||||||
|
M_THREEPID_NOT_FOUND,
|
||||||
|
M_THREEPID_AUTH_FAILED,
|
||||||
|
M_TOO_LARGE,
|
||||||
|
M_MISSING_PARAM,
|
||||||
|
M_UNSUPPORTED_ROOM_VERSION,
|
||||||
|
M_UNRECOGNIZED,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a special response from the Homeserver for errors.
|
||||||
|
class MatrixException implements Exception {
|
||||||
|
final Map<String, dynamic> raw;
|
||||||
|
|
||||||
|
/// The unique identifier for this error.
|
||||||
|
String get errcode => raw["errcode"];
|
||||||
|
|
||||||
|
/// A human readable error description.
|
||||||
|
String get errorMessage => raw["error"];
|
||||||
|
|
||||||
|
/// The frozen request which triggered this Error
|
||||||
|
http.Response response;
|
||||||
|
|
||||||
|
MatrixException(this.response) : this.raw = json.decode(response.body);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "$errcode: $errorMessage";
|
||||||
|
|
||||||
|
/// Returns the [ResponseError]. Is ResponseError.NONE if there wasn't an error.
|
||||||
|
MatrixError get error => MatrixError.values.firstWhere(
|
||||||
|
(e) => e.toString() == 'MatrixError.${(raw["errcode"] ?? "")}',
|
||||||
|
orElse: () => MatrixError.M_UNKNOWN);
|
||||||
|
|
||||||
|
int get retryAfterMs => raw["retry_after_ms"];
|
||||||
|
|
||||||
|
/// This is a session identifier that the client must pass back to the homeserver, if one is provided,
|
||||||
|
/// in subsequent attempts to authenticate in the same API call.
|
||||||
|
String get session => raw["session"];
|
||||||
|
|
||||||
|
/// Returns true if the server requires additional authentication.
|
||||||
|
bool get requireAdditionalAuthentication => response.statusCode == 401;
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// doesn't need additional authentication, then this is null.
|
||||||
|
List<AuthenticationFlow> get authenticationFlows {
|
||||||
|
if (!raw.containsKey("flows") || !(raw["flows"] is List)) return null;
|
||||||
|
List<AuthenticationFlow> flows = [];
|
||||||
|
for (Map<String, dynamic> flow in raw["flows"]) {
|
||||||
|
if (flow["stages"] is List<String>) {
|
||||||
|
flows.add(AuthenticationFlow(flow["stages"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This section contains any information that the client will need to know in order to use a given type
|
||||||
|
/// of authentication. For each authentication type presented, that type may be present as a key in this
|
||||||
|
/// dictionary. For example, the public part of an OAuth client ID could be given here.
|
||||||
|
Map<String, dynamic> get authenticationParams => raw["params"];
|
||||||
|
|
||||||
|
/// Returns the list of already completed authentication flows from previous requests.
|
||||||
|
List<String> get completedAuthenticationFlows => raw["completed"];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
class AuthenticationFlow {
|
||||||
|
final List<String> stages;
|
||||||
|
const AuthenticationFlow(this.stages);
|
||||||
|
}
|
|
@ -30,11 +30,11 @@ import 'package:famedlysdk/src/Presence.dart';
|
||||||
import 'package:famedlysdk/src/Room.dart';
|
import 'package:famedlysdk/src/Room.dart';
|
||||||
import 'package:famedlysdk/src/User.dart';
|
import 'package:famedlysdk/src/User.dart';
|
||||||
import 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
import 'package:famedlysdk/src/requests/SetPushersRequest.dart';
|
||||||
import 'package:famedlysdk/src/responses/ErrorResponse.dart';
|
|
||||||
import 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
import 'package:famedlysdk/src/responses/PushrulesResponse.dart';
|
||||||
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
import 'package:famedlysdk/src/sync/EventUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||||
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
import 'package:famedlysdk/src/sync/UserUpdate.dart';
|
||||||
|
import 'package:famedlysdk/src/utils/MatrixException.dart';
|
||||||
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
import 'package:famedlysdk/src/utils/MatrixFile.dart';
|
||||||
import 'package:famedlysdk/src/utils/Profile.dart';
|
import 'package:famedlysdk/src/utils/Profile.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
@ -60,9 +60,6 @@ void main() {
|
||||||
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
userUpdateListFuture = matrix.connection.onUserEvent.stream.toList();
|
||||||
|
|
||||||
test('Login', () async {
|
test('Login', () async {
|
||||||
Future<ErrorResponse> errorFuture =
|
|
||||||
matrix.connection.onError.stream.first;
|
|
||||||
|
|
||||||
int presenceCounter = 0;
|
int presenceCounter = 0;
|
||||||
int accountDataCounter = 0;
|
int accountDataCounter = 0;
|
||||||
matrix.onPresence = (Presence data) {
|
matrix.onPresence = (Presence data) {
|
||||||
|
@ -72,25 +69,26 @@ void main() {
|
||||||
accountDataCounter++;
|
accountDataCounter++;
|
||||||
};
|
};
|
||||||
|
|
||||||
final bool checkResp1 =
|
expect(matrix.homeserver, null);
|
||||||
|
expect(matrix.matrixVersions, null);
|
||||||
|
|
||||||
|
try {
|
||||||
await matrix.checkServer("https://fakeserver.wrongaddress");
|
await matrix.checkServer("https://fakeserver.wrongaddress");
|
||||||
final bool checkResp2 =
|
} on FormatException catch (exception) {
|
||||||
|
expect(exception != null, true);
|
||||||
|
}
|
||||||
await matrix.checkServer("https://fakeserver.notexisting");
|
await matrix.checkServer("https://fakeserver.notexisting");
|
||||||
|
expect(matrix.homeserver, "https://fakeserver.notexisting");
|
||||||
|
expect(matrix.matrixVersions,
|
||||||
|
["r0.0.1", "r0.1.0", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0"]);
|
||||||
|
|
||||||
ErrorResponse checkError = await errorFuture;
|
final Map<String, dynamic> resp = await matrix.connection
|
||||||
|
|
||||||
expect(checkResp1, false);
|
|
||||||
expect(checkResp2, true);
|
|
||||||
expect(checkError.errcode, "NO_RESPONSE");
|
|
||||||
|
|
||||||
final resp = await matrix.connection
|
|
||||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
.jsonRequest(type: HTTPType.POST, action: "/client/r0/login", data: {
|
||||||
"type": "m.login.password",
|
"type": "m.login.password",
|
||||||
"user": "test",
|
"user": "test",
|
||||||
"password": "1234",
|
"password": "1234",
|
||||||
"initial_device_display_name": "Fluffy Matrix Client"
|
"initial_device_display_name": "Fluffy Matrix Client"
|
||||||
});
|
});
|
||||||
expect(resp is ErrorResponse, false);
|
|
||||||
|
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.connection.onLoginStateChanged.stream.first;
|
||||||
|
@ -176,15 +174,19 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Try to get ErrorResponse', () async {
|
test('Try to get ErrorResponse', () async {
|
||||||
final resp = await matrix.connection
|
MatrixException expectedException;
|
||||||
|
try {
|
||||||
|
await matrix.connection
|
||||||
.jsonRequest(type: HTTPType.PUT, action: "/non/existing/path");
|
.jsonRequest(type: HTTPType.PUT, action: "/non/existing/path");
|
||||||
expect(resp is ErrorResponse, true);
|
} on MatrixException catch (exception) {
|
||||||
|
expectedException = exception;
|
||||||
|
}
|
||||||
|
expect(expectedException.error, MatrixError.M_UNRECOGNIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Logout', () async {
|
test('Logout', () async {
|
||||||
final dynamic resp = await matrix.connection
|
await matrix.connection
|
||||||
.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
.jsonRequest(type: HTTPType.POST, action: "/client/r0/logout");
|
||||||
expect(resp is ErrorResponse, false);
|
|
||||||
|
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.connection.onLoginStateChanged.stream.first;
|
||||||
|
@ -331,8 +333,7 @@ void main() {
|
||||||
test('setAvatar', () async {
|
test('setAvatar', () async {
|
||||||
final MatrixFile testFile =
|
final MatrixFile testFile =
|
||||||
MatrixFile(bytes: [], path: "fake/path/file.jpeg");
|
MatrixFile(bytes: [], path: "fake/path/file.jpeg");
|
||||||
final dynamic resp = await matrix.setAvatar(testFile);
|
await matrix.setAvatar(testFile);
|
||||||
expect(resp, null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getPushrules', () async {
|
test('getPushrules', () async {
|
||||||
|
@ -352,8 +353,7 @@ void main() {
|
||||||
lang: "en",
|
lang: "en",
|
||||||
data: PusherData(
|
data: PusherData(
|
||||||
format: "event_id_only", url: "https://examplepushserver.com"));
|
format: "event_id_only", url: "https://examplepushserver.com"));
|
||||||
final dynamic resp = await matrix.setPushers(data);
|
await matrix.setPushers(data);
|
||||||
expect(resp is ErrorResponse, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('joinRoomById', () async {
|
test('joinRoomById', () async {
|
||||||
|
@ -389,8 +389,13 @@ void main() {
|
||||||
test('Logout when token is unknown', () async {
|
test('Logout when token is unknown', () async {
|
||||||
Future<LoginState> loginStateFuture =
|
Future<LoginState> loginStateFuture =
|
||||||
matrix.connection.onLoginStateChanged.stream.first;
|
matrix.connection.onLoginStateChanged.stream.first;
|
||||||
|
|
||||||
|
try {
|
||||||
await matrix.connection
|
await matrix.connection
|
||||||
.jsonRequest(type: HTTPType.DELETE, action: "/unknown/token");
|
.jsonRequest(type: HTTPType.DELETE, action: "/unknown/token");
|
||||||
|
} on MatrixException catch (exception) {
|
||||||
|
expect(exception.error, MatrixError.M_UNKNOWN_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
LoginState state = await loginStateFuture;
|
LoginState state = await loginStateFuture;
|
||||||
expect(state, LoginState.loggedOut);
|
expect(state, LoginState.loggedOut);
|
||||||
|
|
|
@ -48,12 +48,17 @@ class FakeMatrixApi extends MockClient {
|
||||||
|
|
||||||
if (request.url.origin != "https://fakeserver.notexisting")
|
if (request.url.origin != "https://fakeserver.notexisting")
|
||||||
return Response(
|
return Response(
|
||||||
"<html><head></head><body>Not found...</body></html>", 50);
|
"<html><head></head><body>Not found...</body></html>", 404);
|
||||||
|
|
||||||
// Call API
|
// Call API
|
||||||
if (api.containsKey(method) && api[method].containsKey(action))
|
if (api.containsKey(method) && api[method].containsKey(action))
|
||||||
res = api[method][action](data);
|
res = api[method][action](data);
|
||||||
else
|
else if (method == "GET" &&
|
||||||
|
action.contains("/client/r0/rooms/") &&
|
||||||
|
action.contains("/state/m.room.member/")) {
|
||||||
|
res = {"displayname": ""};
|
||||||
|
return Response(json.encode(res), 200);
|
||||||
|
} else
|
||||||
res = {
|
res = {
|
||||||
"errcode": "M_UNRECOGNIZED",
|
"errcode": "M_UNRECOGNIZED",
|
||||||
"error": "Unrecognized request"
|
"error": "Unrecognized request"
|
||||||
|
@ -62,6 +67,64 @@ class FakeMatrixApi extends MockClient {
|
||||||
return Response(json.encode(res), 100);
|
return Response(json.encode(res), 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static Map<String, dynamic> messagesResponse = {
|
||||||
|
"start": "t47429-4392820_219380_26003_2265",
|
||||||
|
"end": "t47409-4357353_219380_26003_2265",
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "This is an example text message",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<b>This is an example text message</b>"
|
||||||
|
},
|
||||||
|
"type": "m.room.message",
|
||||||
|
"event_id": "3143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!1234:example.com",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {"name": "The room name"},
|
||||||
|
"type": "m.room.name",
|
||||||
|
"event_id": "2143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!1234:example.com",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234},
|
||||||
|
"state_key": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "Gangnam Style",
|
||||||
|
"url": "mxc://example.org/a526eYUSFFxlgbQYZmo442",
|
||||||
|
"info": {
|
||||||
|
"thumbnail_url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe",
|
||||||
|
"thumbnail_info": {
|
||||||
|
"mimetype": "image/jpeg",
|
||||||
|
"size": 46144,
|
||||||
|
"w": 300,
|
||||||
|
"h": 300
|
||||||
|
},
|
||||||
|
"w": 480,
|
||||||
|
"h": 320,
|
||||||
|
"duration": 2140786,
|
||||||
|
"size": 1563685,
|
||||||
|
"mimetype": "video/mp4"
|
||||||
|
},
|
||||||
|
"msgtype": "m.video"
|
||||||
|
},
|
||||||
|
"type": "m.room.message",
|
||||||
|
"event_id": "1143273582443PhrSn:example.org",
|
||||||
|
"room_id": "!1234:example.com",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"unsigned": {"age": 1234}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
static Map<String, dynamic> syncResponse = {
|
static Map<String, dynamic> syncResponse = {
|
||||||
"next_batch": Random().nextDouble().toString(),
|
"next_batch": Random().nextDouble().toString(),
|
||||||
"presence": {
|
"presence": {
|
||||||
|
@ -457,6 +520,8 @@ class FakeMatrixApi extends MockClient {
|
||||||
|
|
||||||
static final Map<String, Map<String, dynamic>> api = {
|
static final Map<String, Map<String, dynamic>> api = {
|
||||||
"GET": {
|
"GET": {
|
||||||
|
"/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/@getme:example.com": (var req) => {
|
||||||
"avatar_url": "mxc://test",
|
"avatar_url": "mxc://test",
|
||||||
"displayname": "You got me",
|
"displayname": "You got me",
|
||||||
|
@ -480,65 +545,10 @@ class FakeMatrixApi extends MockClient {
|
||||||
"origin_server_ts": 1432735824653,
|
"origin_server_ts": 1432735824653,
|
||||||
"unsigned": {"age": 1234}
|
"unsigned": {"age": 1234}
|
||||||
},
|
},
|
||||||
|
"/client/r0/rooms/!localpart:server.abc/messages?from=&dir=b&limit=100&filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D":
|
||||||
|
(var req) => messagesResponse,
|
||||||
"/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100&filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D":
|
"/client/r0/rooms/!1234:example.com/messages?from=1234&dir=b&limit=100&filter=%7B%22room%22:%7B%22state%22:%7B%22lazy_load_members%22:true%7D%7D%7D":
|
||||||
(var req) => {
|
(var req) => messagesResponse,
|
||||||
"start": "t47429-4392820_219380_26003_2265",
|
|
||||||
"end": "t47409-4357353_219380_26003_2265",
|
|
||||||
"chunk": [
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "This is an example text message",
|
|
||||||
"msgtype": "m.text",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<b>This is an example text message</b>"
|
|
||||||
},
|
|
||||||
"type": "m.room.message",
|
|
||||||
"event_id": "3143273582443PhrSn:example.org",
|
|
||||||
"room_id": "!1234:example.com",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"origin_server_ts": 1432735824653,
|
|
||||||
"unsigned": {"age": 1234}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {"name": "The room name"},
|
|
||||||
"type": "m.room.name",
|
|
||||||
"event_id": "2143273582443PhrSn:example.org",
|
|
||||||
"room_id": "!1234:example.com",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"origin_server_ts": 1432735824653,
|
|
||||||
"unsigned": {"age": 1234},
|
|
||||||
"state_key": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "Gangnam Style",
|
|
||||||
"url": "mxc://example.org/a526eYUSFFxlgbQYZmo442",
|
|
||||||
"info": {
|
|
||||||
"thumbnail_url":
|
|
||||||
"mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe",
|
|
||||||
"thumbnail_info": {
|
|
||||||
"mimetype": "image/jpeg",
|
|
||||||
"size": 46144,
|
|
||||||
"w": 300,
|
|
||||||
"h": 300
|
|
||||||
},
|
|
||||||
"w": 480,
|
|
||||||
"h": 320,
|
|
||||||
"duration": 2140786,
|
|
||||||
"size": 1563685,
|
|
||||||
"mimetype": "video/mp4"
|
|
||||||
},
|
|
||||||
"msgtype": "m.video"
|
|
||||||
},
|
|
||||||
"type": "m.room.message",
|
|
||||||
"event_id": "1143273582443PhrSn:example.org",
|
|
||||||
"room_id": "!1234:example.com",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"origin_server_ts": 1432735824653,
|
|
||||||
"unsigned": {"age": 1234}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"/client/versions": (var req) => {
|
"/client/versions": (var req) => {
|
||||||
"versions": [
|
"versions": [
|
||||||
"r0.0.1",
|
"r0.0.1",
|
||||||
|
|
|
@ -29,14 +29,17 @@ import 'package:famedlysdk/src/sync/RoomUpdate.dart';
|
||||||
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
import 'package:famedlysdk/src/utils/ChatTime.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'FakeMatrixApi.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
/// All Tests related to the MxContent
|
/// All Tests related to the MxContent
|
||||||
group("RoomList", () {
|
group("RoomList", () {
|
||||||
final roomID = "!1:example.com";
|
final roomID = "!1:example.com";
|
||||||
|
|
||||||
test("Create and insert one room", () async {
|
test("Create and insert one room", () async {
|
||||||
final Client client = Client("testclient");
|
final Client client = Client("testclient", debug: true);
|
||||||
client.homeserver = "https://testserver.abc";
|
client.connection.httpClient = FakeMatrixApi();
|
||||||
|
await client.checkServer("https://fakeserver.notexisting");
|
||||||
client.prevBatch = "1234";
|
client.prevBatch = "1234";
|
||||||
|
|
||||||
int updateCount = 0;
|
int updateCount = 0;
|
||||||
|
@ -84,8 +87,9 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Restort", () async {
|
test("Restort", () async {
|
||||||
final Client client = Client("testclient");
|
final Client client = Client("testclient", debug: true);
|
||||||
client.homeserver = "https://testserver.abc";
|
client.connection.httpClient = FakeMatrixApi();
|
||||||
|
await client.checkServer("https://fakeserver.notexisting");
|
||||||
client.prevBatch = "1234";
|
client.prevBatch = "1234";
|
||||||
|
|
||||||
int updateCount = 0;
|
int updateCount = 0;
|
||||||
|
@ -186,7 +190,7 @@ void main() {
|
||||||
await new Future.delayed(new Duration(milliseconds: 50));
|
await new Future.delayed(new Duration(milliseconds: 50));
|
||||||
|
|
||||||
expect(updateCount, 5);
|
expect(updateCount, 5);
|
||||||
expect(roomUpdates, 2);
|
expect(roomUpdates, 3);
|
||||||
expect(insertList, [0, 1]);
|
expect(insertList, [0, 1]);
|
||||||
expect(removeList, []);
|
expect(removeList, []);
|
||||||
|
|
||||||
|
@ -224,8 +228,9 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("onlyLeft", () async {
|
test("onlyLeft", () async {
|
||||||
final Client client = Client("testclient");
|
final Client client = Client("testclient", debug: true);
|
||||||
client.homeserver = "https://testserver.abc";
|
client.connection.httpClient = FakeMatrixApi();
|
||||||
|
await client.checkServer("https://fakeserver.notexisting");
|
||||||
client.prevBatch = "1234";
|
client.prevBatch = "1234";
|
||||||
|
|
||||||
int updateCount = 0;
|
int updateCount = 0;
|
||||||
|
@ -268,9 +273,9 @@ void main() {
|
||||||
expect(roomList.eventSub != null, true);
|
expect(roomList.eventSub != null, true);
|
||||||
expect(roomList.roomSub != null, true);
|
expect(roomList.roomSub != null, true);
|
||||||
expect(roomList.rooms[0].id, "2");
|
expect(roomList.rooms[0].id, "2");
|
||||||
expect(updateCount, 2);
|
|
||||||
expect(insertList, [0]);
|
expect(insertList, [0]);
|
||||||
expect(removeList, []);
|
expect(removeList, []);
|
||||||
|
expect(updateCount, 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,9 +145,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sendReadReceipt", () async {
|
test("sendReadReceipt", () async {
|
||||||
final dynamic resp =
|
|
||||||
await room.sendReadReceipt("§1234:fakeServer.notExisting");
|
await room.sendReadReceipt("§1234:fakeServer.notExisting");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("requestParticipants", () async {
|
test("requestParticipants", () async {
|
||||||
|
@ -167,28 +165,25 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("setName", () async {
|
test("setName", () async {
|
||||||
final dynamic resp = await room.setName("Testname");
|
final String eventId = await room.setName("Testname");
|
||||||
expect(resp["event_id"], "42");
|
expect(eventId, "42");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("setDescription", () async {
|
test("setDescription", () async {
|
||||||
final dynamic resp = await room.setDescription("Testname");
|
final String eventId = await room.setDescription("Testname");
|
||||||
expect(resp["event_id"], "42");
|
expect(eventId, "42");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("kick", () async {
|
test("kick", () async {
|
||||||
final dynamic resp = await room.kick("Testname");
|
await room.kick("Testname");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ban", () async {
|
test("ban", () async {
|
||||||
final dynamic resp = await room.ban("Testname");
|
await room.ban("Testname");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("unban", () async {
|
test("unban", () async {
|
||||||
final dynamic resp = await room.unban("Testname");
|
await room.unban("Testname");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("PowerLevels", () async {
|
test("PowerLevels", () async {
|
||||||
|
@ -259,18 +254,17 @@ void main() {
|
||||||
expect(room.canSendEvent("m.room.power_levels"), false);
|
expect(room.canSendEvent("m.room.power_levels"), false);
|
||||||
expect(room.canSendEvent("m.room.member"), false);
|
expect(room.canSendEvent("m.room.member"), false);
|
||||||
expect(room.canSendEvent("m.room.message"), true);
|
expect(room.canSendEvent("m.room.message"), true);
|
||||||
final dynamic resp =
|
final String resp =
|
||||||
await room.setPower("@test:fakeServer.notExisting", 90);
|
await room.setPower("@test:fakeServer.notExisting", 90);
|
||||||
expect(resp["event_id"], "42");
|
expect(resp, "42");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("invite", () async {
|
test("invite", () async {
|
||||||
final dynamic resp = await room.invite("Testname");
|
await room.invite("Testname");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getParticipants", () async {
|
test("getParticipants", () async {
|
||||||
room.states["@alice:test.abc"] = RoomState(
|
room.setState(RoomState(
|
||||||
senderId: "@alice:test.abc",
|
senderId: "@alice:test.abc",
|
||||||
typeKey: "m.room.member",
|
typeKey: "m.room.member",
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
|
@ -278,15 +272,14 @@ void main() {
|
||||||
eventId: "12345",
|
eventId: "12345",
|
||||||
time: ChatTime.now(),
|
time: ChatTime.now(),
|
||||||
content: {"displayname": "alice"},
|
content: {"displayname": "alice"},
|
||||||
stateKey: "@alice:test.abc");
|
stateKey: "@alice:test.abc"));
|
||||||
final List<User> userList = room.getParticipants();
|
final List<User> userList = room.getParticipants();
|
||||||
expect(userList.length, 1);
|
expect(userList.length, 4);
|
||||||
expect(userList[0].displayName, "alice");
|
expect(userList[3].displayName, "alice");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("addToDirectChat", () async {
|
test("addToDirectChat", () async {
|
||||||
final dynamic resp = await room.addToDirectChat("Testname");
|
await room.addToDirectChat("Testname");
|
||||||
expect(resp, {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getTimeline", () async {
|
test("getTimeline", () async {
|
||||||
|
@ -295,7 +288,10 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getUserByMXID", () async {
|
test("getUserByMXID", () async {
|
||||||
final User user = await room.getUserByMXID("@getme:example.com");
|
User user;
|
||||||
|
try {
|
||||||
|
user = await room.getUserByMXID("@getme:example.com");
|
||||||
|
} catch (_) {}
|
||||||
expect(user.stateKey, "@getme:example.com");
|
expect(user.stateKey, "@getme:example.com");
|
||||||
expect(user.calcDisplayname(), "You got me");
|
expect(user.calcDisplayname(), "You got me");
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue