diff --git a/include/AvatarProvider.h b/include/AvatarProvider.h
index ce82f2aa..4b4e15e9 100644
--- a/include/AvatarProvider.h
+++ b/include/AvatarProvider.h
@@ -20,15 +20,17 @@
#include <QImage>
#include <functional>
-class AvatarProvider : public QObject
+class AvatarProxy : public QObject
{
Q_OBJECT
-public:
- //! The callback is called with the downloaded avatar for the given user
- //! or the avatar is downloaded first and then saved for re-use.
- static void resolve(const QString &room_id,
- const QString &userId,
- QObject *receiver,
- std::function<void(QImage)> callback);
+signals:
+ void avatarDownloaded(const QByteArray &data);
};
+
+using AvatarCallback = std::function<void(QImage)>;
+
+namespace AvatarProvider {
+void
+resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb);
+}
diff --git a/include/Cache.h b/include/Cache.h
index d2574b76..f5a655cf 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -17,13 +17,18 @@
#pragma once
-#include <QDebug>
+#include <boost/optional.hpp>
+
#include <QDir>
#include <QImage>
+
#include <json.hpp>
#include <lmdb++.h>
#include <mtx/events/join_rules.hpp>
#include <mtx/responses.hpp>
+#include <mtxclient/crypto/client.hpp>
+#include <mutex>
+
using mtx::events::state::JoinRule;
struct RoomMember
@@ -140,6 +145,82 @@ struct RoomSearchResult
Q_DECLARE_METATYPE(RoomSearchResult)
Q_DECLARE_METATYPE(RoomInfo)
+// Extra information associated with an outbound megolm session.
+struct OutboundGroupSessionData
+{
+ std::string session_id;
+ std::string session_key;
+ uint64_t message_index = 0;
+};
+
+inline void
+to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
+{
+ obj["session_id"] = msg.session_id;
+ obj["session_key"] = msg.session_key;
+ obj["message_index"] = msg.message_index;
+}
+
+inline void
+from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
+{
+ msg.session_id = obj.at("session_id");
+ msg.session_key = obj.at("session_key");
+ msg.message_index = obj.at("message_index");
+}
+
+struct OutboundGroupSessionDataRef
+{
+ OlmOutboundGroupSession *session;
+ OutboundGroupSessionData data;
+};
+
+struct DevicePublicKeys
+{
+ std::string ed25519;
+ std::string curve25519;
+};
+
+inline void
+to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
+{
+ obj["ed25519"] = msg.ed25519;
+ obj["curve25519"] = msg.curve25519;
+}
+
+inline void
+from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
+{
+ msg.ed25519 = obj.at("ed25519");
+ msg.curve25519 = obj.at("curve25519");
+}
+
+//! Represents a unique megolm session identifier.
+struct MegolmSessionIndex
+{
+ //! The room in which this session exists.
+ std::string room_id;
+ //! The session_id of the megolm session.
+ std::string session_id;
+ //! The curve25519 public key of the sender.
+ std::string sender_key;
+
+ //! Representation to be used in a hash map.
+ std::string to_hash() const { return room_id + session_id + sender_key; }
+};
+
+struct OlmSessionStorage
+{
+ // Megolm sessions
+ std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
+ std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
+ std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
+
+ // Guards for accessing megolm sessions.
+ std::mutex group_outbound_mtx;
+ std::mutex group_inbound_mtx;
+};
+
class Cache : public QObject
{
Q_OBJECT
@@ -192,7 +273,7 @@ public:
void saveState(const mtx::responses::Sync &res);
bool isInitialized() const;
- QString nextBatchToken() const;
+ std::string nextBatchToken() const;
void deleteData();
@@ -206,6 +287,9 @@ public:
bool isFormatValid();
void setCurrentFormat();
+ //! Retrieve all the user ids from a room.
+ std::vector<std::string> roomMembers(const std::string &room_id);
+
//! Check if the given user has power leve greater than than
//! lowest power level of the given events.
bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
@@ -237,6 +321,7 @@ public:
{
return image(QString::fromStdString(url));
}
+ void saveImage(const std::string &url, const std::string &data);
void saveImage(const QString &url, const QByteArray &data);
RoomInfo singleRoomInfo(const std::string &room_id);
@@ -259,6 +344,51 @@ public:
//! Check if we have sent a desktop notification for the given event id.
bool isNotificationSent(const std::string &event_id);
+ //! Mark a room that uses e2e encryption.
+ void setEncryptedRoom(const std::string &room_id);
+ bool isRoomEncrypted(const std::string &room_id);
+
+ //! Save the public keys for a device.
+ void saveDeviceKeys(const std::string &device_id);
+ void getDeviceKeys(const std::string &device_id);
+
+ //! Save the device list for a user.
+ void setDeviceList(const std::string &user_id, const std::vector<std::string> &devices);
+ std::vector<std::string> getDeviceList(const std::string &user_id);
+
+ //
+ // Outbound Megolm Sessions
+ //
+ void saveOutboundMegolmSession(const std::string &room_id,
+ const OutboundGroupSessionData &data,
+ mtx::crypto::OutboundGroupSessionPtr session);
+ OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
+ bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
+ void updateOutboundMegolmSession(const std::string &room_id, int message_index);
+
+ //
+ // Inbound Megolm Sessions
+ //
+ void saveInboundMegolmSession(const MegolmSessionIndex &index,
+ mtx::crypto::InboundGroupSessionPtr session);
+ OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
+ bool inboundMegolmSessionExists(const MegolmSessionIndex &index) noexcept;
+
+ //
+ // Olm Sessions
+ //
+ void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
+ std::vector<std::string> getOlmSessions(const std::string &curve25519);
+ boost::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
+ const std::string &session_id);
+
+ void saveOlmAccount(const std::string &pickled);
+ std::string restoreOlmAccount();
+
+ void restoreSessions();
+
+ OlmSessionStorage session_storage;
+
private:
//! Save an invited room.
void saveInvite(lmdb::txn &txn,
@@ -431,6 +561,16 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
+ //! Retrieves or creates the database that stores the open OLM sessions between our device
+ //! and the given curve25519 key which represents another device.
+ //!
+ //! Each entry is a map from the session_id to the pickled representation of the session.
+ lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
+ {
+ return lmdb::dbi::open(
+ txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
+ }
+
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
{
if (!event.content.display_name.empty())
@@ -450,6 +590,12 @@ private:
lmdb::dbi readReceiptsDb_;
lmdb::dbi notificationsDb_;
+ lmdb::dbi devicesDb_;
+ lmdb::dbi deviceKeysDb_;
+
+ lmdb::dbi inboundMegolmSessionDb_;
+ lmdb::dbi outboundMegolmSessionDb_;
+
QString localUserId_;
QString cacheDirectory_;
};
diff --git a/include/ChatPage.h b/include/ChatPage.h
index b6c431e4..ffea2914 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -17,6 +17,8 @@
#pragma once
+#include <atomic>
+
#include <QFrame>
#include <QHBoxLayout>
#include <QMap>
@@ -27,8 +29,7 @@
#include "Cache.h"
#include "CommunitiesList.h"
#include "Community.h"
-
-#include <mtx.hpp>
+#include "MatrixClient.h"
class OverlayModal;
class QuickSwitcher;
@@ -50,9 +51,6 @@ constexpr int CONSENSUS_TIMEOUT = 1000;
constexpr int SHOW_CONTENT_TIMEOUT = 3000;
constexpr int TYPING_REFRESH_TIMEOUT = 10000;
-Q_DECLARE_METATYPE(mtx::responses::Rooms)
-Q_DECLARE_METATYPE(std::vector<std::string>)
-
class ChatPage : public QWidget
{
Q_OBJECT
@@ -71,7 +69,37 @@ public:
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
void deleteConfigs();
+public slots:
+ void leaveRoom(const QString &room_id);
+
signals:
+ void connectionLost();
+ void connectionRestored();
+
+ void notificationsRetrieved(const mtx::responses::Notifications &);
+
+ void uploadFailed(const QString &msg);
+ void imageUploaded(const QString &roomid,
+ const QString &filename,
+ const QString &url,
+ const QString &mime,
+ qint64 dsize);
+ void fileUploaded(const QString &roomid,
+ const QString &filename,
+ const QString &url,
+ const QString &mime,
+ qint64 dsize);
+ void audioUploaded(const QString &roomid,
+ const QString &filename,
+ const QString &url,
+ const QString &mime,
+ qint64 dsize);
+ void videoUploaded(const QString &roomid,
+ const QString &filename,
+ const QString &url,
+ const QString &mime,
+ qint64 dsize);
+
void contentLoaded();
void closing();
void changeWindowTitle(const QString &msg);
@@ -82,30 +110,50 @@ signals:
void showOverlayProgressBar();
void startConsesusTimer();
+ void removeTimelineEvent(const QString &room_id, const QString &event_id);
+
+ void ownProfileOk();
+ void setUserDisplayName(const QString &name);
+ void setUserAvatar(const QImage &avatar);
+ void loggedOut();
+
+ void trySyncCb();
+ void tryDelayedSyncCb();
+ void tryInitialSyncCb();
+ void leftRoom(const QString &room_id);
+
void initializeRoomList(QMap<QString, RoomInfo>);
void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews(const std::vector<std::string> &rooms);
void syncUI(const mtx::responses::Rooms &rooms);
- void continueSync(const QString &next_batch);
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
void syncTopBar(const std::map<QString, RoomInfo> &updates);
+ void dropToLoginPageCb(const QString &msg);
private slots:
void showUnreadMessageNotification(int count);
void updateTopBarAvatar(const QString &roomid, const QPixmap &img);
- void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name);
void updateOwnCommunitiesInfo(const QList<QString> &own_communities);
- void initialSyncCompleted(const mtx::responses::Sync &response);
- void syncCompleted(const mtx::responses::Sync &response);
void changeTopRoomInfo(const QString &room_id);
void logout();
void removeRoom(const QString &room_id);
- //! Handles initial sync failures.
- void retryInitialSync(int status_code = -1);
+ void dropToLoginPage(const QString &msg);
+
+ void joinRoom(const QString &room);
+ void createRoom(const mtx::requests::CreateRoom &req);
+ void sendTypingNotifications();
private:
static ChatPage *instance_;
+ //! Handler callback for initial sync. It doesn't run on the main thread so all
+ //! communication with the GUI should be done through signals.
+ void initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err);
+ void tryInitialSync();
+ void trySync();
+ void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
+ void getProfileInfo();
+
//! Check if the given room is currently open.
bool isRoomActive(const QString &room_id)
{
@@ -161,8 +209,8 @@ private:
// Safety net if consensus is not possible or too slow.
QTimer *showContentTimer_;
QTimer *consensusTimer_;
- QTimer *syncTimeoutTimer_;
- QTimer *initialSyncTimer_;
+ QTimer connectivityTimer_;
+ std::atomic_bool isConnected_;
QString current_room_;
QString current_community_;
diff --git a/include/CommunitiesList.h b/include/CommunitiesList.h
index 3299e7c4..78b9602e 100644
--- a/include/CommunitiesList.h
+++ b/include/CommunitiesList.h
@@ -23,12 +23,14 @@ public:
signals:
void communityChanged(const QString &id);
+ void avatarRetrieved(const QString &id, const QPixmap &img);
public slots:
void updateCommunityAvatar(const QString &id, const QPixmap &img);
void highlightSelectedCommunity(const QString &id);
private:
+ void fetchCommunityAvatar(const QString &id, const QString &avatarUrl);
void addGlobalItem() { addCommunity(QSharedPointer<Community>(new Community), "world"); }
//! Check whether or not a community id is currently managed.
diff --git a/include/Logging.hpp b/include/Logging.hpp
new file mode 100644
index 00000000..2feae60d
--- /dev/null
+++ b/include/Logging.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <memory>
+#include <spdlog/spdlog.h>
+
+namespace nhlog {
+void
+init(const std::string &file);
+
+std::shared_ptr<spdlog::logger>
+ui();
+
+std::shared_ptr<spdlog::logger>
+net();
+
+std::shared_ptr<spdlog::logger>
+db();
+
+std::shared_ptr<spdlog::logger>
+crypto();
+}
diff --git a/include/LoginPage.h b/include/LoginPage.h
index 34a08df9..c52ccaa4 100644
--- a/include/LoginPage.h
+++ b/include/LoginPage.h
@@ -28,6 +28,12 @@ class OverlayModal;
class RaisedButton;
class TextField;
+namespace mtx {
+namespace responses {
+struct Login;
+}
+}
+
class LoginPage : public QWidget
{
Q_OBJECT
@@ -42,12 +48,19 @@ signals:
void loggingIn();
void errorOccurred();
+ //! Used to trigger the corresponding slot outside of the main thread.
+ void versionErrorCb(const QString &err);
+ void loginErrorCb(const QString &err);
+ void versionOkCb();
+
+ void loginOk(const mtx::responses::Login &res);
+
protected:
void paintEvent(QPaintEvent *event) override;
public slots:
// Displays errors produced during the login.
- void loginError(QString msg) { error_label_->setText(msg); }
+ void loginError(const QString &msg) { error_label_->setText(msg); }
private slots:
// Callback for the back button.
@@ -63,13 +76,25 @@ private slots:
void onServerAddressEntered();
// Callback for errors produced during server probing
- void versionError(QString error_message);
-
+ void versionError(const QString &error_message);
// Callback for successful server probing
- void versionSuccess();
+ void versionOk();
private:
bool isMatrixIdValid();
+ void checkHomeserverVersion();
+ std::string initialDeviceName()
+ {
+#if defined(Q_OS_MAC)
+ return "nheko on macOS";
+#elif defined(Q_OS_LINUX)
+ return "nheko on Linux";
+#elif defined(Q_OS_WIN)
+ return "nheko on Windows";
+#else
+ return "nheko";
+#endif
+ }
QVBoxLayout *top_layout_;
diff --git a/include/MainWindow.h b/include/MainWindow.h
index 0fbc7567..b068e8f6 100644
--- a/include/MainWindow.h
+++ b/include/MainWindow.h
@@ -96,7 +96,7 @@ private slots:
void showUserSettingsPage() { pageStack_->setCurrentWidget(userSettingsPage_); }
//! Show the chat page and start communicating with the given access token.
- void showChatPage(QString user_id, QString home_server, QString token);
+ void showChatPage();
void showOverlayProgressBar();
void removeOverlayProgressBar();
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index eae57281..7ea5e0b7 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -1,287 +1,28 @@
-/*
- * nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
- *
- * This program 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.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
#pragma once
-#include <QFileInfo>
-#include <QJsonDocument>
-#include <QNetworkAccessManager>
-#include <QNetworkReply>
-#include <QNetworkRequest>
-#include <QUrl>
-#include <memory>
-#include <mtx.hpp>
-#include <mtx/errors.hpp>
-
-class DownloadMediaProxy : public QObject
-{
- Q_OBJECT
-
-signals:
- void imageDownloaded(const QPixmap &data);
- void fileDownloaded(const QByteArray &data);
- void avatarDownloaded(const QImage &img);
-};
+#include <QMetaType>
-class StateEventProxy : public QObject
-{
- Q_OBJECT
-
-signals:
- void stateEventSent();
- void stateEventError(const QString &msg);
-};
+#include <mtx/responses.hpp>
+#include <mtxclient/http/client.hpp>
+Q_DECLARE_METATYPE(mtx::responses::Login)
+Q_DECLARE_METATYPE(mtx::responses::Messages)
+Q_DECLARE_METATYPE(mtx::responses::Notifications)
+Q_DECLARE_METATYPE(mtx::responses::Rooms)
Q_DECLARE_METATYPE(mtx::responses::Sync)
+Q_DECLARE_METATYPE(std::string)
+Q_DECLARE_METATYPE(std::vector<std::string>)
-/*
- * MatrixClient provides the high level API to communicate with
- * a Matrix homeserver. All the responses are returned through signals.
- */
-class MatrixClient : public QNetworkAccessManager
-{
- Q_OBJECT
-public:
- MatrixClient(QObject *parent = 0);
-
- // Client API.
- void initialSync() noexcept;
- void sync() noexcept;
- template<class EventBody, mtx::events::EventType EventT>
- std::shared_ptr<StateEventProxy> sendStateEvent(const EventBody &body,
- const QString &roomId,
- const QString &stateKey = "");
- void sendRoomMessage(mtx::events::MessageType ty,
- int txnId,
- const QString &roomid,
- const QString &msg,
- const QString &mime,
- uint64_t media_size,
- const QString &url = "") noexcept;
- void login(const QString &username, const QString &password) noexcept;
- void registerUser(const QString &username,
- const QString &password,
- const QString &server,
- const QString &session = "") noexcept;
- void versions() noexcept;
- void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
- //! Download user's avatar.
- QSharedPointer<DownloadMediaProxy> fetchUserAvatar(const QUrl &avatarUrl);
- void fetchCommunityAvatar(const QString &communityId, const QUrl &avatarUrl);
- void fetchCommunityProfile(const QString &communityId);
- void fetchCommunityRooms(const QString &communityId);
- QSharedPointer<DownloadMediaProxy> downloadImage(const QUrl &url);
- QSharedPointer<DownloadMediaProxy> downloadFile(const QUrl &url);
- void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept;
- void uploadImage(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data);
- void uploadFile(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data);
- void uploadAudio(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data);
- void uploadVideo(const QString &roomid,
- const QString &filename,
- const QSharedPointer<QIODevice> data);
- void uploadFilter(const QString &filter) noexcept;
- void joinRoom(const QString &roomIdOrAlias);
- void leaveRoom(const QString &roomId);
- void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
- void removeTypingNotification(const QString &roomid);
- void readEvent(const QString &room_id, const QString &event_id);
- void redactEvent(const QString &room_id, const QString &event_id);
- void inviteUser(const QString &room_id, const QString &user);
- void createRoom(const mtx::requests::CreateRoom &request);
- void getNotifications() noexcept;
-
- QUrl getHomeServer() { return server_; };
- int transactionId() { return txn_id_; };
- int incrementTransactionId() { return ++txn_id_; };
-
- void reset() noexcept;
-
-public slots:
- void getOwnProfile() noexcept;
- void getOwnCommunities() noexcept;
- void logout() noexcept;
-
- void setServer(const QString &server)
- {
- server_ = QUrl(QString("%1://%2").arg(serverProtocol_).arg(server));
- };
- void setAccessToken(const QString &token) { token_ = token; };
- void setNextBatchToken(const QString &next_batch) { next_batch_ = next_batch; };
-
-signals:
- void loginError(const QString &error);
- void registerError(const QString &error);
- void registrationFlow(const QString &user,
- const QString &pass,
- const QString &server,
- const QString &session);
- void versionError(const QString &error);
-
- void loggedOut();
- void invitedUser(const QString &room_id, const QString &user);
- void roomCreated(const QString &room_id);
-
- void loginSuccess(const QString &userid, const QString &homeserver, const QString &token);
- void registerSuccess(const QString &userid,
- const QString &homeserver,
- const QString &token);
- void versionSuccess();
- void uploadFailed(int statusCode, const QString &msg);
- void imageUploaded(const QString &roomid,
- const QString &filename,
- const QString &url,
- const QString &mime,
- uint64_t size);
- void fileUploaded(const QString &roomid,
- const QString &filename,
- const QString &url,
- const QString &mime,
- uint64_t size);
- void audioUploaded(const QString &roomid,
- const QString &filename,
- const QString &url,
- const QString &mime,
- uint64_t size);
- void videoUploaded(const QString &roomid,
- const QString &filename,
- const QString &url,
- const QString &mime,
- uint64_t size);
- void roomAvatarRetrieved(const QString &roomid,
- const QPixmap &img,
- const QString &url,
- const QByteArray &data);
- void userAvatarRetrieved(const QString &userId, const QImage &img);
- void communityAvatarRetrieved(const QString &communityId, const QPixmap &img);
- void communityProfileRetrieved(const QString &communityId, const QJsonObject &profile);
- void communityRoomsRetrieved(const QString &communityId, const QJsonObject &rooms);
-
- // Returned profile data for the user's account.
- void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name);
- void getOwnCommunitiesResponse(const QList<QString> &own_communities);
- void initialSyncCompleted(const mtx::responses::Sync &response);
- void initialSyncFailed(int status_code = -1);
- void syncCompleted(const mtx::responses::Sync &response);
- void syncFailed(const QString &msg);
- void joinFailed(const QString &msg);
- void messageSent(const QString &event_id, const QString &roomid, int txn_id);
- void messageSendFailed(const QString &roomid, int txn_id);
- void emoteSent(const QString &event_id, const QString &roomid, int txn_id);
- void messagesRetrieved(const QString &room_id, const mtx::responses::Messages &msgs);
- void joinedRoom(const QString &room_id);
- void leftRoom(const QString &room_id);
- void roomCreationFailed(const QString &msg);
-
- void redactionFailed(const QString &error);
- void redactionCompleted(const QString &room_id, const QString &event_id);
- void invalidToken();
- void syncError(const QString &error);
- void notificationsRetrieved(const mtx::responses::Notifications ¬ifications);
-
-private:
- QNetworkReply *makeUploadRequest(QSharedPointer<QIODevice> iodev);
- QJsonObject getUploadReply(QNetworkReply *reply);
- void setupAuth(QNetworkRequest &req)
- {
- req.setRawHeader("Authorization", QString("Bearer %1").arg(token_).toLocal8Bit());
- }
-
- // Client API prefix.
- QString clientApiUrl_;
-
- // Media API prefix.
- QString mediaApiUrl_;
-
- // The Matrix server used for communication.
- QUrl server_;
-
- // The access token used for authentication.
- QString token_;
-
- // Increasing transaction ID.
- int txn_id_;
+namespace http {
+namespace v2 {
+mtx::http::Client *
+client();
- //! Token to be used for the next sync.
- QString next_batch_;
- //! http or https (default).
- QString serverProtocol_;
- //! Filter to be send as filter-param for (initial) /sync requests.
- QString filter_;
-};
+bool
+is_logged_in();
+}
-namespace http {
//! Initialize the http module
void
init();
-
-//! Retrieve the client instance.
-MatrixClient *
-client();
-}
-
-template<class EventBody, mtx::events::EventType EventT>
-std::shared_ptr<StateEventProxy>
-MatrixClient::sendStateEvent(const EventBody &body, const QString &roomId, const QString &stateKey)
-{
- QUrl endpoint(server_);
- endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/state/%2/%3")
- .arg(roomId)
- .arg(QString::fromStdString(to_string(EventT)))
- .arg(stateKey));
-
- QNetworkRequest request(QString(endpoint.toEncoded()));
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- setupAuth(request);
-
- auto proxy = std::shared_ptr<StateEventProxy>(new StateEventProxy,
- [](StateEventProxy *p) { p->deleteLater(); });
-
- auto serializedBody = nlohmann::json(body).dump();
- auto reply = put(request, QByteArray(serializedBody.data(), serializedBody.size()));
- connect(reply, &QNetworkReply::finished, this, [reply, proxy]() {
- reply->deleteLater();
-
- int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- auto data = reply->readAll();
-
- if (status == 0 || status >= 400) {
- try {
- mtx::errors::Error res = nlohmann::json::parse(data);
- emit proxy->stateEventError(QString::fromStdString(res.error));
- } catch (const std::exception &e) {
- emit proxy->stateEventError(QString::fromStdString(e.what()));
- }
-
- return;
- }
-
- try {
- mtx::responses::EventId res = nlohmann::json::parse(data);
- emit proxy->stateEventSent();
- } catch (const std::exception &e) {
- emit proxy->stateEventError(QString::fromStdString(e.what()));
- }
- });
-
- return proxy;
}
diff --git a/include/Olm.hpp b/include/Olm.hpp
new file mode 100644
index 00000000..6f871628
--- /dev/null
+++ b/include/Olm.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include <memory>
+#include <mtx.hpp>
+#include <mtxclient/crypto/client.hpp>
+
+constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
+
+namespace olm {
+
+struct OlmCipherContent
+{
+ std::string body;
+ uint8_t type;
+};
+
+inline void
+from_json(const nlohmann::json &obj, OlmCipherContent &msg)
+{
+ msg.body = obj.at("body");
+ msg.type = obj.at("type");
+}
+
+struct OlmMessage
+{
+ std::string sender_key;
+ std::string sender;
+
+ using RecipientKey = std::string;
+ std::map<RecipientKey, OlmCipherContent> ciphertext;
+};
+
+inline void
+from_json(const nlohmann::json &obj, OlmMessage &msg)
+{
+ if (obj.at("type") != "m.room.encrypted")
+ throw std::invalid_argument("invalid type for olm message");
+
+ if (obj.at("content").at("algorithm") != OLM_ALGO)
+ throw std::invalid_argument("invalid algorithm for olm message");
+
+ msg.sender = obj.at("sender");
+ msg.sender_key = obj.at("content").at("sender_key");
+ msg.ciphertext =
+ obj.at("content").at("ciphertext").get<std::map<std::string, OlmCipherContent>>();
+}
+
+mtx::crypto::OlmClient *
+client();
+
+void
+handle_to_device_messages(const std::vector<nlohmann::json> &msgs);
+
+boost::optional<json>
+try_olm_decryption(const std::string &sender_key, const OlmCipherContent &content);
+
+void
+handle_olm_message(const OlmMessage &msg);
+
+//! Establish a new inbound megolm session with the decrypted payload from olm.
+void
+create_inbound_megolm_session(const std::string &sender,
+ const std::string &sender_key,
+ const nlohmann::json &payload);
+
+void
+handle_pre_key_olm_message(const std::string &sender,
+ const std::string &sender_key,
+ const OlmCipherContent &content);
+
+mtx::events::msg::Encrypted
+encrypt_group_message(const std::string &room_id,
+ const std::string &device_id,
+ const std::string &body);
+
+} // namespace olm
diff --git a/include/RegisterPage.h b/include/RegisterPage.h
index f4d97816..d02de7c4 100644
--- a/include/RegisterPage.h
+++ b/include/RegisterPage.h
@@ -44,6 +44,11 @@ signals:
void backButtonClicked();
void errorOccurred();
void registering();
+ void registerOk();
+ void registerErrorCb(const QString &msg);
+ void registrationFlow(const std::string &user,
+ const std::string &pass,
+ const std::string &session);
private slots:
void onBackButtonClicked();
diff --git a/include/RoomList.h b/include/RoomList.h
index 98d9443e..59b0e865 100644
--- a/include/RoomList.h
+++ b/include/RoomList.h
@@ -60,6 +60,8 @@ signals:
void acceptInvite(const QString &room_id);
void declineInvite(const QString &room_id);
void roomAvatarChanged(const QString &room_id, const QPixmap &img);
+ void joinRoom(const QString &room_id);
+ void updateRoomAvatarCb(const QString &room_id, const QPixmap &img);
public slots:
void updateRoomAvatar(const QString &roomid, const QPixmap &img);
diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h
index c679b9b2..af58c2c3 100644
--- a/include/TextInputWidget.h
+++ b/include/TextInputWidget.h
@@ -129,6 +129,16 @@ public:
QColor borderColor() const { return borderColor_; }
void setBorderColor(QColor &color) { borderColor_ = color; }
+ void disableInput()
+ {
+ input_->setEnabled(false);
+ input_->setPlaceholderText(tr("Connection lost. Nheko is trying to re-connect..."));
+ }
+ void enableInput()
+ {
+ input_->setEnabled(true);
+ input_->setPlaceholderText(tr("Write a message..."));
+ }
public slots:
void openFileSelection();
diff --git a/include/dialogs/ReCaptcha.hpp b/include/dialogs/ReCaptcha.hpp
index 1eda40c7..5f47b0eb 100644
--- a/include/dialogs/ReCaptcha.hpp
+++ b/include/dialogs/ReCaptcha.hpp
@@ -12,7 +12,7 @@ class ReCaptcha : public QWidget
Q_OBJECT
public:
- ReCaptcha(const QString &server, const QString &session, QWidget *parent = nullptr);
+ ReCaptcha(const QString &session, QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
diff --git a/include/dialogs/RoomSettings.hpp b/include/dialogs/RoomSettings.hpp
index 375a531e..6cab03b7 100644
--- a/include/dialogs/RoomSettings.hpp
+++ b/include/dialogs/RoomSettings.hpp
@@ -5,16 +5,17 @@
#include "Cache.h"
+class Avatar;
class FlatButton;
-class TextField;
+class QComboBox;
class QHBoxLayout;
-class Avatar;
-class QPixmap;
-class QLayout;
class QLabel;
-class QComboBox;
-class TextField;
class QLabel;
+class QLayout;
+class QPixmap;
+class TextField;
+class TextField;
+class Toggle;
template<class T>
class QSharedPointer;
@@ -30,6 +31,9 @@ public:
signals:
void nameChanged(const QString &roomName);
+ void nameEventSentCb(const QString &newName);
+ void topicEventSentCb();
+ void stateEventErrorCb(const QString &msg);
private:
QString roomId_;
@@ -81,6 +85,7 @@ public:
signals:
void closing();
+ void enableEncryptionError(const QString &msg);
protected:
void paintEvent(QPaintEvent *event) override;
@@ -95,13 +100,15 @@ private:
void setupEditButton();
//! Retrieve the current room information from cache.
void retrieveRoomInfo();
+ void enableEncryption();
//! Whether the user would be able to change the name or the topic of the room.
- bool hasEditRights_ = true;
+ bool hasEditRights_ = true;
+ bool usesEncryption_ = false;
QHBoxLayout *editLayout_;
// Button section
- FlatButton *saveBtn_;
+ FlatButton *okBtn_;
FlatButton *cancelBtn_;
FlatButton *editFieldsBtn_;
@@ -113,6 +120,7 @@ private:
TopSection *topSection_;
QComboBox *accessCombo;
+ Toggle *encryptionToggle_;
};
} // dialogs
diff --git a/include/timeline/TimelineItem.h b/include/timeline/TimelineItem.h
index 9997ec1d..f055f217 100644
--- a/include/timeline/TimelineItem.h
+++ b/include/timeline/TimelineItem.h
@@ -193,16 +193,17 @@ public:
QString eventId() const { return event_id_; }
void setEventId(const QString &event_id) { event_id_ = event_id; }
void markReceived();
+ bool isReceived() { return isReceived_; };
void setRoomId(QString room_id) { room_id_ = room_id; }
- void sendReadReceipt() const
- {
- if (!event_id_.isEmpty())
- http::client()->readEvent(room_id_, event_id_);
- }
+ void sendReadReceipt() const;
//! Add a user avatar for this event.
void addAvatar();
+signals:
+ void eventRedacted(const QString &event_id);
+ void redactionFailed(const QString &msg);
+
protected:
void paintEvent(QPaintEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
@@ -225,6 +226,10 @@ private:
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
+ //! Whether or not the event associated with the widget
+ //! has been acknowledged by the server.
+ bool isReceived_ = false;
+
QString replaceEmoji(const QString &body);
QString event_id_;
QString room_id_;
diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h
index e6e35ccb..a86c0286 100644
--- a/include/timeline/TimelineView.h
+++ b/include/timeline/TimelineView.h
@@ -18,7 +18,6 @@
#pragma once
#include <QApplication>
-#include <QDebug>
#include <QLayout>
#include <QList>
#include <QQueue>
@@ -34,6 +33,19 @@
#include "ScrollBar.h"
#include "TimelineItem.h"
+class StateKeeper
+{
+public:
+ StateKeeper(std::function<void()> &&fn)
+ : fn_(std::move(fn))
+ {}
+
+ ~StateKeeper() { fn_(); }
+
+private:
+ std::function<void()> fn_;
+};
+
class FloatingButton;
struct DescInfo;
@@ -42,33 +54,44 @@ struct DescInfo;
struct PendingMessage
{
mtx::events::MessageType ty;
- int txn_id;
+ std::string txn_id;
QString body;
QString filename;
QString mime;
uint64_t media_size;
QString event_id;
TimelineItem *widget;
-
- PendingMessage(mtx::events::MessageType ty,
- int txn_id,
- QString body,
- QString filename,
- QString mime,
- uint64_t media_size,
- QString event_id,
- TimelineItem *widget)
- : ty(ty)
- , txn_id(txn_id)
- , body(body)
- , filename(filename)
- , mime(mime)
- , media_size(media_size)
- , event_id(event_id)
- , widget(widget)
- {}
+ bool is_encrypted = false;
};
+template<class MessageT>
+MessageT
+toRoomMessage(const PendingMessage &) = delete;
+
+template<>
+mtx::events::msg::Audio
+toRoomMessage<mtx::events::msg::Audio>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::Emote
+toRoomMessage<mtx::events::msg::Emote>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::File
+toRoomMessage<mtx::events::msg::File>(const PendingMessage &);
+
+template<>
+mtx::events::msg::Image
+toRoomMessage<mtx::events::msg::Image>(const PendingMessage &m);
+
+template<>
+mtx::events::msg::Text
+toRoomMessage<mtx::events::msg::Text>(const PendingMessage &);
+
+template<>
+mtx::events::msg::Video
+toRoomMessage<mtx::events::msg::Video>(const PendingMessage &m);
+
// In which place new TimelineItems should be inserted.
enum class TimelineDirection
{
@@ -129,7 +152,7 @@ public:
const QString &filename,
const QString &mime,
uint64_t size);
- void updatePendingMessage(int txn_id, QString event_id);
+ void updatePendingMessage(const std::string &txn_id, const QString &event_id);
void scrollDown();
QLabel *createDateSeparator(QDateTime datetime);
@@ -142,18 +165,21 @@ public slots:
void fetchHistory();
// Add old events at the top of the timeline.
- void addBackwardsEvents(const QString &room_id, const mtx::responses::Messages &msgs);
+ void addBackwardsEvents(const mtx::responses::Messages &msgs);
// Whether or not the initial batch has been loaded.
bool hasLoaded() { return scroll_layout_->count() > 1 || isTimelineFinished; }
- void handleFailedMessage(int txnid);
+ void handleFailedMessage(const std::string &txn_id);
private slots:
void sendNextPendingMessage();
signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info);
+ void messagesRetrieved(const mtx::responses::Messages &res);
+ void messageFailed(const std::string &txn_id);
+ void messageSent(const std::string &txn_id, const QString &event_id);
protected:
void paintEvent(QPaintEvent *event) override;
@@ -165,6 +191,25 @@ private:
QWidget *relativeWidget(TimelineItem *item, int dt) const;
+ TimelineEvent parseEncryptedEvent(
+ const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
+
+ void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
+ const std::string &room_key,
+ const DevicePublicKeys &pks,
+ const std::string &user_id,
+ const std::string &device_id,
+ const mtx::responses::ClaimKeys &res,
+ mtx::http::RequestErr err);
+
+ //! Callback for all message sending.
+ void sendRoomMessageHandler(const std::string &txn_id,
+ const mtx::responses::EventId &res,
+ mtx::http::RequestErr err);
+ void prepareEncryptedMessage(const PendingMessage &msg);
+
+ //! Call the /messages endpoint to fill the timeline.
+ void getMessages();
//! HACK: Fixing layout flickering when adding to the bottom
//! of the timeline.
void pushTimelineItem(TimelineItem *item)
@@ -230,8 +275,10 @@ private:
uint64_t origin_server_ts,
TimelineDirection direction);
- bool isPendingMessage(const QString &txnid, const QString &sender, const QString &userid);
- void removePendingMessage(const QString &txnid);
+ bool isPendingMessage(const std::string &txn_id,
+ const QString &sender,
+ const QString &userid);
+ void removePendingMessage(const std::string &txn_id);
bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); }
@@ -315,14 +362,18 @@ TimelineView::addUserMessage(const QString &url,
lastMessageDirection_ = TimelineDirection::Bottom;
- QApplication::processEvents();
-
// Keep track of the sender and the timestamp of the current message.
saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
- int txn_id = http::client()->incrementTransactionId();
+ PendingMessage message;
+ message.ty = MsgType;
+ message.txn_id = http::v2::client()->generate_txn_id();
+ message.body = url;
+ message.filename = trimmed;
+ message.mime = mime;
+ message.media_size = size;
+ message.widget = view_item;
- PendingMessage message(MsgType, txn_id, url, trimmed, mime, size, "", view_item);
handleNewUserMessage(message);
}
@@ -351,10 +402,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender);
- const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
- if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
+ const auto txn_id = event.unsigned_data.transaction_id;
+ if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) {
- removePendingMessage(txnid);
+ removePendingMessage(txn_id);
return nullptr;
}
@@ -376,10 +427,10 @@ TimelineView::processMessageEvent(const Event &event, TimelineDirection directio
const auto event_id = QString::fromStdString(event.event_id);
const auto sender = QString::fromStdString(event.sender);
- const QString txnid = QString::fromStdString(event.unsigned_data.transaction_id);
- if ((!txnid.isEmpty() && isPendingMessage(txnid, sender, local_user_)) ||
+ const auto txn_id = event.unsigned_data.transaction_id;
+ if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
isDuplicate(event_id)) {
- removePendingMessage(txnid);
+ removePendingMessage(txn_id);
return nullptr;
}
diff --git a/include/timeline/TimelineViewManager.h b/include/timeline/TimelineViewManager.h
index 308b83aa..9e31ecbf 100644
--- a/include/timeline/TimelineViewManager.h
+++ b/include/timeline/TimelineViewManager.h
@@ -56,6 +56,8 @@ signals:
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
public slots:
+ void removeTimelineEvent(const QString &room_id, const QString &event_id);
+
void setHistoryView(const QString &room_id);
void queueTextMessage(const QString &msg);
void queueEmoteMessage(const QString &msg);
@@ -80,10 +82,6 @@ public slots:
const QString &mime,
uint64_t dsize);
-private slots:
- void messageSent(const QString &eventid, const QString &roomid, int txnid);
- void messageSendFailed(const QString &roomid, int txnid);
-
private:
//! Check if the given room id is managed by a TimelineView.
bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); }
diff --git a/include/timeline/widgets/AudioItem.h b/include/timeline/widgets/AudioItem.h
index b31385d1..7b0781a2 100644
--- a/include/timeline/widgets/AudioItem.h
+++ b/include/timeline/widgets/AudioItem.h
@@ -69,9 +69,14 @@ protected:
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
+signals:
+ void fileDownloadedCb(const QByteArray &data);
+
+private slots:
+ void fileDownloaded(const QByteArray &data);
+
private:
void init();
- void fileDownloaded(const QByteArray &data);
enum class AudioState
{
diff --git a/include/timeline/widgets/FileItem.h b/include/timeline/widgets/FileItem.h
index 09181d32..66543e79 100644
--- a/include/timeline/widgets/FileItem.h
+++ b/include/timeline/widgets/FileItem.h
@@ -52,15 +52,20 @@ public:
QColor iconColor() const { return iconColor_; }
QColor backgroundColor() const { return backgroundColor_; }
+signals:
+ void fileDownloadedCb(const QByteArray &data);
+
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
+private slots:
+ void fileDownloaded(const QByteArray &data);
+
private:
void openUrl();
void init();
- void fileDownloaded(const QByteArray &data);
QUrl url_;
QString text_;
diff --git a/include/timeline/widgets/ImageItem.h b/include/timeline/widgets/ImageItem.h
index b17b2d8b..e9d823f4 100644
--- a/include/timeline/widgets/ImageItem.h
+++ b/include/timeline/widgets/ImageItem.h
@@ -40,13 +40,17 @@ public:
uint64_t size,
QWidget *parent = nullptr);
- void setImage(const QPixmap &image);
-
QSize sizeHint() const override;
public slots:
//! Show a save as dialog for the image.
void saveAs();
+ void setImage(const QPixmap &image);
+ void saveImage(const QString &filename, const QByteArray &data);
+
+signals:
+ void imageDownloaded(const QPixmap &img);
+ void imageSaved(const QString &filename, const QByteArray &data);
protected:
void paintEvent(QPaintEvent *event) override;
@@ -57,7 +61,9 @@ protected:
bool isInteractive_ = true;
private:
+ void init();
void openUrl();
+ void downloadMedia(const QUrl &url);
int max_width_ = 500;
int max_height_ = 300;
|