1// SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net>
2// SPDX-FileCopyrightText: 2017 Roman Plášil <me@rplasil.name>
3// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
4// SPDX-License-Identifier: LGPL-2.1-or-later
5
6#pragma once
7
8#include "quotient_common.h"
9#include "ssosession.h"
10#include "util.h"
11
12#include "csapi/create_room.h"
13#include "csapi/login.h"
14
15#include "events/accountdataevents.h"
16
17#include <QtCore/QDir>
18#include <QtCore/QObject>
19#include <QtCore/QSize>
20#include <QtCore/QUrl>
21
22#include <functional>
23
24#ifdef Quotient_E2EE_ENABLED
25#include "e2ee/e2ee_common.h"
26#include "e2ee/qolmoutboundsession.h"
27#include "keyverificationsession.h"
28#include "events/keyverificationevent.h"
29#endif
30
31Q_DECLARE_METATYPE(Quotient::GetLoginFlowsJob::LoginFlow)
32
33namespace Quotient {
34
35class Room;
36class User;
37class ConnectionData;
38class RoomEvent;
39
40class SyncJob;
41class SyncData;
42class RoomMessagesJob;
43class PostReceiptJob;
44class ForgetRoomJob;
45class MediaThumbnailJob;
46class JoinRoomJob;
47class UploadContentJob;
48class GetContentJob;
49class DownloadFileJob;
50class SendToDeviceJob;
51class SendMessageJob;
52class LeaveRoomJob;
53class Database;
54struct EncryptedFileMetadata;
55
56class QOlmAccount;
57class QOlmInboundGroupSession;
58
59using LoginFlow = GetLoginFlowsJob::LoginFlow;
60
61//! Predefined login flows
62namespace LoginFlows {
63 inline const LoginFlow Password { .type: "m.login.password"_ls };
64 inline const LoginFlow SSO { .type: "m.login.sso"_ls };
65 inline const LoginFlow Token { .type: "m.login.token"_ls };
66}
67
68// To simplify comparisons of LoginFlows
69
70inline bool operator==(const LoginFlow& lhs, const LoginFlow& rhs)
71{
72 return lhs.type == rhs.type;
73}
74
75inline bool operator!=(const LoginFlow& lhs, const LoginFlow& rhs)
76{
77 return !(lhs == rhs);
78}
79
80class Connection;
81
82using room_factory_t =
83 std::function<Room*(Connection*, const QString&, JoinState)>;
84using user_factory_t = std::function<User*(Connection*, const QString&)>;
85
86//! \brief The default factory to create room objects
87//!
88//! Just a wrapper around operator new.
89//! \sa Connection::setRoomFactory, Connection::setRoomType
90template <typename T = Room>
91auto defaultRoomFactory(Connection* c, const QString& id, JoinState js)
92{
93 return new T(c, id, js);
94}
95
96//! \brief The default factory to create user objects
97//!
98//! Just a wrapper around operator new.
99//! \sa Connection::setUserFactory, Connection::setUserType
100template <typename T = User>
101auto defaultUserFactory(Connection* c, const QString& id)
102{
103 return new T(id, c);
104}
105
106// Room ids, rather than room pointers, are used in the direct chat
107// map types because the library keeps Invite rooms separate from
108// rooms in Join and Leave state; and direct chats in account data
109// are stored with no regard to their state.
110using DirectChatsMap = QMultiHash<const User*, QString>;
111using DirectChatUsersMap = QMultiHash<QString, User*>;
112using IgnoredUsersList = IgnoredUsersEvent::value_type;
113
114class QUOTIENT_API Connection : public QObject {
115 Q_OBJECT
116
117 Q_PROPERTY(User* localUser READ user NOTIFY stateChanged)
118 Q_PROPERTY(QString localUserId READ userId NOTIFY stateChanged)
119 Q_PROPERTY(QString domain READ domain NOTIFY stateChanged STORED false)
120 Q_PROPERTY(QString deviceId READ deviceId NOTIFY stateChanged)
121 Q_PROPERTY(QByteArray accessToken READ accessToken NOTIFY stateChanged)
122 Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY stateChanged STORED false)
123 Q_PROPERTY(QString defaultRoomVersion READ defaultRoomVersion NOTIFY capabilitiesLoaded)
124 Q_PROPERTY(QUrl homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
125 Q_PROPERTY(QVector<GetLoginFlowsJob::LoginFlow> loginFlows READ loginFlows NOTIFY loginFlowsChanged)
126 Q_PROPERTY(bool isUsable READ isUsable NOTIFY loginFlowsChanged STORED false)
127 Q_PROPERTY(bool supportsSso READ supportsSso NOTIFY loginFlowsChanged STORED false)
128 Q_PROPERTY(bool supportsPasswordAuth READ supportsPasswordAuth NOTIFY loginFlowsChanged STORED false)
129 Q_PROPERTY(bool cacheState READ cacheState WRITE setCacheState NOTIFY cacheStateChanged)
130 Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged)
131 Q_PROPERTY(bool canChangePassword READ canChangePassword NOTIFY capabilitiesLoaded)
132 Q_PROPERTY(bool encryptionEnabled READ encryptionEnabled WRITE enableEncryption NOTIFY encryptionChanged)
133
134public:
135 using UsersToDevicesToContent = QHash<QString, QHash<QString, QJsonObject>>;
136
137 enum RoomVisibility {
138 PublishRoom,
139 UnpublishRoom
140 }; // FIXME: Should go inside CreateRoomJob
141
142 explicit Connection(QObject* parent = nullptr);
143 explicit Connection(const QUrl& server, QObject* parent = nullptr);
144 ~Connection() override;
145
146 //! \brief Get all rooms known within this Connection
147 //!
148 //! This includes Invite, Join and Leave rooms, in no particular order.
149 //! \note Leave rooms will only show up in the list if they have been left
150 //! in the same running session. The library doesn't cache left rooms
151 //! between runs and it doesn't retrieve the full list of left rooms
152 //! from the server.
153 //! \sa rooms, room, roomsWithTag
154 Q_INVOKABLE QVector<Quotient::Room*> allRooms() const;
155
156 //! \brief Get rooms that have either of the given join state(s)
157 //!
158 //! This method returns, in no particular order, rooms which join state
159 //! matches the mask passed in \p joinStates.
160 //! \note Similar to allRooms(), this won't retrieve the full list of
161 //! Leave rooms from the server.
162 //! \sa allRooms, room, roomsWithTag
163 Q_INVOKABLE QVector<Quotient::Room*>
164 rooms(Quotient::JoinStates joinStates) const;
165
166 //! Get the total number of rooms in the given join state(s)
167 Q_INVOKABLE int roomsCount(Quotient::JoinStates joinStates) const;
168
169 //! \brief Check whether the account has data of the given type
170 //!
171 //! Direct chats map is not supported by this method _yet_.
172 bool hasAccountData(const QString& type) const;
173
174 //! \brief Get a generic account data event of the given type
175 //!
176 //! \return an account data event of the given type stored on the server,
177 //! or nullptr if there's none of that type.
178 //! \note Direct chats map cannot be retrieved using this method _yet_;
179 //! use directChats() instead.
180 const EventPtr& accountData(const QString& type) const;
181
182 //! \brief Get an account data event of the given type
183 //!
184 //! \return the account data content for the given event type stored
185 //! on the server, or a default-constructed object if there's none
186 //! of that type.
187 //! \note Direct chats map cannot be retrieved using this method _yet_;
188 //! use directChats() instead.
189 template <EventClass EventT>
190 const EventT* accountData() const
191 {
192 return eventCast<EventT>(accountData(EventT::TypeId));
193 }
194
195 //! \brief Get account data as a JSON object
196 //!
197 //! This returns the content part of the account data event
198 //! of the given type. Direct chats map cannot be retrieved using
199 //! this method _yet_; use directChats() instead.
200 Q_INVOKABLE QJsonObject accountDataJson(const QString& type) const;
201
202 //! Set a generic account data event of the given type
203 void setAccountData(EventPtr&& event);
204
205 Q_INVOKABLE void setAccountData(const QString& type,
206 const QJsonObject& content);
207
208 //! \brief Get all Invited and Joined rooms grouped by tag
209 //! \return a hashmap from tag name to a vector of room pointers,
210 //! sorted by their order in the tag - details are at
211 //! https://spec.matrix.org/v1.5/client-server-api/#room-tagging
212 QHash<QString, QVector<Room*>> tagsToRooms() const;
213
214 //! Get all room tags known on this connection
215 QStringList tagNames() const;
216
217 //! Get the list of rooms with the specified tag
218 QVector<Room*> roomsWithTag(const QString& tagName) const;
219
220 //! \brief Mark the room as a direct chat with the user
221 //!
222 //! This function marks \p room as a direct chat with \p user.
223 //! Emits the signal synchronously, without waiting to complete
224 //! synchronisation with the server.
225 //! \sa directChatsListChanged
226 void addToDirectChats(const Room* room, User* user);
227
228 //! \brief Unmark the room from direct chats
229 //!
230 //! This function removes the room id from direct chats either for
231 //! a specific \p user or for all users if \p user in nullptr.
232 //! The room id is used to allow removal of, e.g., ids of forgotten
233 //! rooms; a Room object need not exist. Emits the signal
234 //! immediately, without waiting to complete synchronisation with
235 //! the server.
236 //! \sa directChatsListChanged
237 void removeFromDirectChats(const QString& roomId, User* user = nullptr);
238
239 //! Check whether the room id corresponds to a direct chat
240 bool isDirectChat(const QString& roomId) const;
241
242 //! Get the whole map from users to direct chat rooms
243 DirectChatsMap directChats() const;
244
245 //! \brief Retrieve the list of users the room is a direct chat with
246 //! \return The list of users for which this room is marked as
247 //! a direct chat; an empty list if the room is not a direct chat
248 QList<User*> directChatUsers(const Room* room) const;
249
250 //! Check whether a particular user id is in the ignore list
251 Q_INVOKABLE bool isIgnored(const QString& userId) const;
252
253 //! Check whether a particular user is in the ignore list
254 Q_INVOKABLE bool isIgnored(const Quotient::User* user) const;
255
256 //! Get the whole list of ignored users
257 Q_INVOKABLE Quotient::IgnoredUsersList ignoredUsers() const;
258
259 //! \brief Add the user to the ignore list
260 //! The change signal is emitted synchronously, without waiting
261 //! to complete synchronisation with the server.
262 //!
263 //! \sa ignoredUsersListChanged
264 Q_INVOKABLE void addToIgnoredUsers(const Quotient::User* user);
265
266 //! \brief Remove the user from the ignore list
267 //!
268 //! Similar to adding, the change signal is emitted synchronously.
269 //! \sa ignoredUsersListChanged
270 Q_INVOKABLE void removeFromIgnoredUsers(const Quotient::User* user);
271
272 //! Get the full list of users known to this account
273 QMap<QString, User*> users() const;
274
275 //! Get the base URL of the homeserver to connect to
276 QUrl homeserver() const;
277 //! Get the domain name used for ids/aliases on the server
278 QString domain() const;
279 //! Check if the homeserver is known to be reachable and working
280 bool isUsable() const;
281 //! Get the list of supported login flows
282 QVector<GetLoginFlowsJob::LoginFlow> loginFlows() const;
283 //! Check whether the current homeserver supports password auth
284 bool supportsPasswordAuth() const;
285 //! Check whether the current homeserver supports SSO
286 bool supportsSso() const;
287 //! Find a room by its id and a mask of applicable states
288 Q_INVOKABLE Quotient::Room* room(
289 const QString& roomId,
290 Quotient::JoinStates states = JoinState::Invite | JoinState::Join) const;
291 //! Find a room by its alias and a mask of applicable states
292 Q_INVOKABLE Quotient::Room* roomByAlias(
293 const QString& roomAlias,
294 Quotient::JoinStates states = JoinState::Invite | JoinState::Join) const;
295 //! \brief Update the internal map of room aliases to IDs
296 //!
297 //! This is used to maintain the internal index of room aliases.
298 //! It does NOT change aliases on the server,
299 //! \sa Room::setLocalAliases
300 void updateRoomAliases(const QString& roomId,
301 const QStringList& previousRoomAliases,
302 const QStringList& roomAliases);
303 Q_INVOKABLE Quotient::Room* invitation(const QString& roomId) const;
304 Q_INVOKABLE Quotient::User* user(const QString& uId);
305 const User* user() const;
306 User* user();
307 QString userId() const;
308 QString deviceId() const;
309 QByteArray accessToken() const;
310 bool isLoggedIn() const;
311#ifdef Quotient_E2EE_ENABLED
312 QOlmAccount* olmAccount() const;
313 Database* database() const;
314
315 UnorderedMap<QByteArray, QOlmInboundGroupSession> loadRoomMegolmSessions(
316 const Room* room) const;
317 void saveMegolmSession(const Room* room,
318 const QOlmInboundGroupSession& session) const;
319 [[deprecated("Use database()->loadCurrentOutboundMegolmSession()")]]
320 Omittable<QOlmOutboundGroupSession> loadCurrentOutboundMegolmSession(
321 const QString& roomId) const;
322 [[deprecated("Use database()->saveCurrentOutboundMegolmSession()")]]
323 void saveCurrentOutboundMegolmSession(
324 const QString& roomId, const QOlmOutboundGroupSession& session) const;
325
326 QString edKeyForUserDevice(const QString& userId,
327 const QString& deviceId) const;
328 bool hasOlmSession(const QString& user, const QString& deviceId) const;
329
330 // This assumes that an olm session already exists. If it doesn't, no message is sent.
331 void sendToDevice(const QString& targetUserId, const QString& targetDeviceId,
332 const Event& event, bool encrypted);
333
334 //! Returns true if this megolm session comes from a verified device
335 bool isVerifiedSession(const QByteArray& megolmSessionId) const;
336
337 //! Returns whether the device is verified
338 bool isVerifiedDevice(const QString& userId, const QString& deviceId) const;
339
340 //! \brief Returns whether the device is known and supports end-to-end encryption.
341 //!
342 //! This might give unexpected results for users we're not tracking,
343 //! i.e., users that we don't share an encrypted room with
344 bool isKnownE2eeCapableDevice(const QString& userId, const QString& deviceId) const;
345
346
347 void sendSessionKeyToDevices(const QString& roomId,
348 const QOlmOutboundGroupSession& outboundSession,
349 const QMultiHash<QString, QString>& devices);
350
351 QJsonObject decryptNotification(const QJsonObject &notification);
352 QStringList devicesForUser(const QString& userId) const;
353 Q_INVOKABLE bool isQueryingKeys() const;
354#endif // Quotient_E2EE_ENABLED
355 Q_INVOKABLE Quotient::SyncJob* syncJob() const;
356 Q_INVOKABLE QString nextBatchToken() const;
357 Q_INVOKABLE int millisToReconnect() const;
358
359 Q_INVOKABLE void getTurnServers();
360
361 struct SupportedRoomVersion {
362 QString id;
363 QString status;
364
365 static const QString StableTag; // "stable", as of CS API 0.5
366 bool isStable() const { return status == StableTag; }
367
368 friend QDebug operator<<(QDebug dbg, const SupportedRoomVersion& v)
369 {
370 QDebugStateSaver _(dbg);
371 return dbg.nospace() << v.id << '/' << v.status;
372 }
373 };
374
375 //! Find out if capabilites are still loading from the server
376 Q_INVOKABLE bool loadingCapabilities() const;
377
378 //! \brief Get the room version recommended by the server
379 //!
380 //! Only works after server capabilities have been loaded.
381 //! \sa loadingCapabilities
382 QString defaultRoomVersion() const;
383 //! \brief Get the room version considered stable by the server
384 //!
385 //! Only works after server capabilities have been loaded.
386 //! \sa loadingCapabilities
387 QStringList stableRoomVersions() const;
388 //! \brief Get all room versions supported by the server
389 //! Only works after server capabilities have been loaded.
390 //! \sa loadingCapabilities
391 QVector<SupportedRoomVersion> availableRoomVersions() const;
392
393 //! Indicate if the user can change its password from the client.
394 //! This is often not the case when SSO is enabled.
395 //! \sa loadingCapabilities
396 bool canChangePassword() const;
397
398 //! \brief Check whether encryption is enabled on this connection
399 //!
400 //! There are two conditions for encryption to be enabled:
401 //! 1) the library must be compiled with Quotient_E2EE_ENABLED;
402 //! 2) encryption should be switched on explicitly with enableEncryption()
403 //!
404 //! The reason for the latter is E2EE still being in beta state as of 0.8,
405 //! so clients are strongly advised to keep E2EE off by default and warn
406 //! users opting in that their E2EE things may fall apart, leak etc.
407 //!
408 //! \sa enableEncryption
409 bool encryptionEnabled() const;
410
411 //! \brief Enable or disable encryption on this connection
412 //!
413 //! \note This has no effect if the library is compiled without E2EE support
414 //!
415 //! \sa encryptionEnabled
416 void enableEncryption(bool enable);
417
418 //! \brief Load room state from a previously saved file
419 //!
420 //! Call this before first sync.
421 //! \sa saveState
422 Q_INVOKABLE void loadState();
423
424 //! \brief Save the current state for all rooms to a file
425 //!
426 //! This method saves the current state of rooms (but not messages
427 //! in them) to a local cache file, so that it could be loaded by
428 //! loadState() on a next run of the client.
429 //! \sa loadState
430 Q_INVOKABLE void saveState() const;
431
432 //! This method saves the current state of a single room.
433 void saveRoomState(Room* r) const;
434
435 //! \brief Get the default directory path to save the room state to
436 //! \sa stateCacheDir
437 Q_INVOKABLE QString stateCachePath() const;
438
439 //! \brief Get the default directory to save the room state to
440 //!
441 //! This function returns the default directory to store the cached
442 //! room state, defined as follows:
443 //! \code
444 //! QStandardPaths::writeableLocation(QStandardPaths::CacheLocation) +
445 //! _safeUserId + "_state.json" \endcode where `_safeUserId` is userId() with
446 //! `:` (colon) replaced by
447 //! `_` (underscore), as colons are reserved characters on Windows.
448 //! \sa loadState, saveState, stateCachePath
449 QDir stateCacheDir() const;
450
451 //! \brief Whether or not the rooms state should be cached locally
452 //! \sa loadState, saveState
453 bool cacheState() const;
454 void setCacheState(bool newValue);
455
456 bool lazyLoading() const;
457 void setLazyLoading(bool newValue);
458
459 //! Start a pre-created job object on this connection
460 Q_INVOKABLE BaseJob* run(BaseJob* job,
461 RunningPolicy runningPolicy = ForegroundRequest);
462
463 //! \brief Start a job of a given type with specified arguments and policy
464 //!
465 //! This is a universal method to create and start a job of a type passed
466 //! as a template parameter. The policy allows to fine-tune the way
467 //! the job is executed - as of this writing it means a choice
468 //! between "foreground" and "background".
469 //!
470 //! \param runningPolicy controls how the job is executed
471 //! \param jobArgs arguments to the job constructor
472 //!
473 //! \sa BaseJob::isBackground. QNetworkRequest::BackgroundRequestAttribute
474 template <typename JobT, typename... JobArgTs>
475 JobT* callApi(RunningPolicy runningPolicy, JobArgTs&&... jobArgs)
476 {
477 auto job = new JobT(std::forward<JobArgTs>(jobArgs)...);
478 run(job, runningPolicy);
479 return job;
480 }
481
482 //! \brief Start a job of a specified type with specified arguments
483 //!
484 //! This is an overload that runs the job with "foreground" policy.
485 template <typename JobT, typename... JobArgTs>
486 JobT* callApi(JobArgTs&&... jobArgs)
487 {
488 return callApi<JobT>(ForegroundRequest,
489 std::forward<JobArgTs>(jobArgs)...);
490 }
491
492 //! \brief Get a request URL for a job with specified type and arguments
493 //!
494 //! This calls JobT::makeRequestUrl() prepending the connection's homeserver
495 //! to the list of arguments.
496 template <typename JobT, typename... JobArgTs>
497 QUrl getUrlForApi(JobArgTs&&... jobArgs) const
498 {
499 return JobT::makeRequestUrl(homeserver(),
500 std::forward<JobArgTs>(jobArgs)...);
501 }
502
503 //! \brief Start a local HTTP server and generate a single sign-on URL
504 //!
505 //! This call does the preparatory steps to carry out single sign-on
506 //! sequence
507 //! \sa https://matrix.org/docs/guides/sso-for-client-developers
508 //! \return A proxy object holding two URLs: one for SSO on the chosen
509 //! homeserver and another for the local callback address. Normally
510 //! you won't need the callback URL unless you proxy the response
511 //! with a custom UI. You do not need to delete the SsoSession
512 //! object; the Connection that issued it will dispose of it once
513 //! the login sequence completes (with any outcome).
514 Q_INVOKABLE SsoSession* prepareForSso(const QString& initialDeviceName,
515 const QString& deviceId = {});
516
517 //! \brief Generate a new transaction id
518 //!
519 //! Transaction id's are unique within a single Connection object
520 Q_INVOKABLE QString generateTxnId() const;
521
522 //! Convert an mxc: URL into a CS API URL
523 Q_INVOKABLE QUrl makeMediaUrl(QUrl mxcUrl) const;
524
525 Q_INVOKABLE bool roomSucceeds(const QString& maybePredecessorId,
526 const QString& maybeSuccessorId) const;
527
528#ifdef Quotient_E2EE_ENABLED
529 //! Set the E2EE default state for any Connection created further
530 static void setEncryptionDefault(bool useByDefault);
531#endif
532
533 //! Set a room factory function
534 static void setRoomFactory(room_factory_t f);
535
536 //! Set a user factory function
537 static void setUserFactory(user_factory_t f);
538
539 //! Get a room factory function
540 static room_factory_t roomFactory();
541
542 //! Get a user factory function
543 static user_factory_t userFactory();
544
545 //! Set the room factory to default with the overriden room type
546 template <typename T>
547 static void setRoomType()
548 {
549 setRoomFactory(defaultRoomFactory<T>);
550 }
551
552 //! Set the user factory to default with the overriden user type
553 template <typename T>
554 static void setUserType()
555 {
556 setUserFactory(defaultUserFactory<T>);
557 }
558
559public Q_SLOTS:
560 //! \brief Set the homeserver base URL and retrieve its login flows
561 //!
562 //! \sa LoginFlowsJob, loginFlows, loginFlowsChanged, homeserverChanged
563 void setHomeserver(const QUrl& baseUrl);
564
565 //! \brief Determine and set the homeserver from MXID
566 //!
567 //! This attempts to resolve the homeserver by requesting
568 //! .well-known/matrix/client record from the server taken from the MXID
569 //! serverpart. If there is no record found, the serverpart itself is
570 //! attempted as the homeserver base URL; if the record is there but
571 //! is malformed (e.g., the homeserver base URL cannot be found in it)
572 //! resolveError() is emitted and further processing stops. Otherwise,
573 //! setHomeserver is called, preparing the Connection object for the login
574 //! attempt.
575 //! \param mxid user Matrix ID, such as @someone:example.org
576 //! \sa setHomeserver, homeserverChanged, loginFlowsChanged, resolveError
577 void resolveServer(const QString& mxid);
578
579 //! \brief Log in using a username and password pair
580 //!
581 //! Before logging in, this method checks if the homeserver is valid and
582 //! supports the password login flow. If the homeserver is invalid but
583 //! a full user MXID is provided, this method calls resolveServer() using
584 //! this MXID.
585 //! \sa resolveServer, resolveError, loginError
586 void loginWithPassword(const QString& userId, const QString& password,
587 const QString& initialDeviceName,
588 const QString& deviceId = {});
589
590 //! \brief Log in using a login token
591 //!
592 //! One usual case for this method is the final stage of logging in via SSO.
593 //! Unlike loginWithPassword() and assumeIdentity(), this method cannot
594 //! resolve the server from the user name because the full user MXID is
595 //! encoded in the login token. Callers should ensure the homeserver
596 //! sanity in advance.
597 void loginWithToken(const QString& loginToken,
598 const QString& initialDeviceName,
599 const QString& deviceId = {});
600
601 //! \brief Use an existing access token to connect to the homeserver
602 //!
603 //! This is the old signature that will be deprecated in 0.8; since 0.7.2,
604 //! device id is resolved from the access token. Use 2-arg assumeIdentity()
605 [[deprecated("Device id is now resolved from accessToken; use "
606 "assumeIdentity(QString, QString) instead of this overload")]] //
607 void assumeIdentity(const QString& mxId, const QString& accessToken,
608 [[maybe_unused]] const QString& deviceId);
609
610 //! \brief Use an existing access token to connect to the homeserver
611 //!
612 //! Similar to loginWithPassword(), this method checks that the homeserver
613 //! URL is valid and tries to resolve it from the MXID in case it is not.
614 //! \since 0.7.2
615 void assumeIdentity(const QString& mxId, const QString& accessToken);
616
617 //! Explicitly request capabilities from the server
618 void reloadCapabilities();
619
620 void logout();
621
622 void sync(int timeout = -1);
623 void syncLoop(int timeout = 30000);
624
625 void stopSync();
626
627 virtual MediaThumbnailJob*
628 getThumbnail(const QString& mediaId, QSize requestedSize,
629 RunningPolicy policy = BackgroundRequest);
630 MediaThumbnailJob* getThumbnail(const QUrl& url, QSize requestedSize,
631 RunningPolicy policy = BackgroundRequest);
632 MediaThumbnailJob* getThumbnail(const QUrl& url, int requestedWidth,
633 int requestedHeight,
634 RunningPolicy policy = BackgroundRequest);
635
636 // QIODevice* should already be open
637 UploadContentJob* uploadContent(QIODevice* contentSource,
638 const QString& filename = {},
639 const QString& overrideContentType = {});
640 UploadContentJob* uploadFile(const QString& fileName,
641 const QString& overrideContentType = {});
642 GetContentJob* getContent(const QString& mediaId);
643 GetContentJob* getContent(const QUrl& url);
644 // If localFilename is empty, a temporary file will be created
645 DownloadFileJob* downloadFile(const QUrl& url,
646 const QString& localFilename = {});
647
648#ifdef Quotient_E2EE_ENABLED
649 DownloadFileJob* downloadFile(const QUrl& url,
650 const EncryptedFileMetadata& fileMetadata,
651 const QString& localFilename = {});
652#endif
653
654 //! \brief Create a room (generic method)
655 //!
656 //! This method allows to customize room entirely to your liking,
657 //! providing all the attributes the original CS API provides.
658 CreateRoomJob*
659 createRoom(RoomVisibility visibility, const QString& alias,
660 const QString& name, const QString& topic, QStringList invites,
661 const QString& presetName = {}, const QString& roomVersion = {},
662 bool isDirect = false,
663 const QVector<CreateRoomJob::StateEvent>& initialState = {},
664 const QVector<CreateRoomJob::Invite3pid>& invite3pids = {},
665 const QJsonObject& creationContent = {});
666
667 //! \brief Get a direct chat with a single user
668 //!
669 //! This method may return synchronously or asynchoronously depending
670 //! on whether a direct chat room with the respective person exists
671 //! already.
672 //! \sa directChatAvailable
673 void requestDirectChat(const QString& userId);
674
675 //! \brief Get a direct chat with a single user
676 //!
677 //! This method may return synchronously or asynchoronously depending
678 //! on whether a direct chat room with the respective person exists
679 //! already.
680 //! \sa directChatAvailable
681 void requestDirectChat(User* u);
682
683 //! \brief Run an operation in a direct chat with the user
684 //!
685 //! This method may return synchronously or asynchoronously depending
686 //! on whether a direct chat room with the respective person exists
687 //! already. Instead of emitting a signal it executes the passed
688 //! function object with the direct chat room as its parameter.
689 void doInDirectChat(const QString& userId,
690 const std::function<void(Room*)>& operation);
691
692 //! \brief Run an operation in a direct chat with the user
693 //!
694 //! This method may return synchronously or asynchoronously depending
695 //! on whether a direct chat room with the respective person exists
696 //! already. Instead of emitting a signal it executes the passed
697 //! function object with the direct chat room as its parameter.
698 void doInDirectChat(User* u, const std::function<void(Room*)>& operation);
699
700 //! Create a direct chat with a single user, optional name and topic
701 //!
702 //! A room will always be created, unlike in requestDirectChat.
703 //! It is advised to use requestDirectChat as a default way of getting
704 //! one-on-one with a person, and only use createDirectChat when
705 //! a new creation is explicitly desired.
706 CreateRoomJob* createDirectChat(const QString& userId,
707 const QString& topic = {},
708 const QString& name = {});
709
710 virtual JoinRoomJob* joinRoom(const QString& roomAlias,
711 const QStringList& serverNames = {});
712
713 //! \brief Send /forget to the server and delete room locally
714 //!
715 //! This method is in Connection, not in Room, since it's a
716 //! room lifecycle operation, and Connection is an acting room manager.
717 //! It ensures that the local user is not a member of a room (running /leave,
718 //! if necessary) then issues a /forget request and if that one doesn't fail
719 //! deletion of the local Room object is ensured.
720 //! \param id the room id to forget
721 //! \return the ongoing /forget request to the server; note that the
722 //! success() signal of this request is connected to deleteLater()
723 //! of a respective room so by the moment this finishes, there
724 //! might be no Room object anymore.
725 ForgetRoomJob* forgetRoom(const QString& id);
726
727 SendToDeviceJob* sendToDevices(const QString& eventType,
728 const UsersToDevicesToContent& contents);
729
730 [[deprecated("This method is experimental and may be removed any time")]] //
731 SendMessageJob* sendMessage(const QString& roomId, const RoomEvent& event);
732
733 //! \deprecated Do not use this directly, use Room::leaveRoom() instead
734 virtual LeaveRoomJob* leaveRoom(Room* room);
735
736#ifdef Quotient_E2EE_ENABLED
737 KeyVerificationSession* startKeyVerificationSession(const QString& userId,
738 const QString& deviceId);
739
740 void encryptionUpdate(const Room* room, const QList<User*>& invited = {});
741#endif
742
743 static Connection* makeMockConnection(const QString& mxId,
744 bool enableEncryption = E2EE_Enabled);
745
746Q_SIGNALS:
747 //! \brief Initial server resolution has failed
748 //!
749 //! This signal is emitted when resolveServer() did not manage to resolve
750 //! the homeserver using its .well-known/client record or otherwise.
751 //! \sa resolveServer
752 void resolveError(QString error);
753
754 void homeserverChanged(QUrl baseUrl);
755 void loginFlowsChanged();
756 void capabilitiesLoaded();
757
758 void connected();
759 void loggedOut();
760
761 //! \brief Login data or state have changed
762 //!
763 //! This is a common change signal for userId, deviceId and
764 //! accessToken - these properties normally only change at
765 //! a successful login and logout and are constant at other times.
766 void stateChanged();
767 void loginError(QString message, QString details);
768
769 //! \brief A network request (job) started by callApi() has failed
770 //! \param request the pointer to the failed job
771 //! \sa callApi
772 void requestFailed(Quotient::BaseJob* request);
773
774 //! \brief A network request (job) failed due to network problems
775 //!
776 //! This is _only_ emitted when the job will retry on its own;
777 //! once it gives up, requestFailed() will be emitted.
778 //!
779 //! \param message message about the network problem
780 //! \param details raw error details, if any available
781 //! \param retriesTaken how many retries have already been taken
782 //! \param nextRetryInMilliseconds when the job will retry again
783 //! (-1 if no next retry is scheduled)
784 void networkError(QString message, QString details, int retriesTaken,
785 int nextRetryInMilliseconds);
786
787 void syncDone();
788 void syncError(QString message, QString details);
789
790 void newUser(Quotient::User* user);
791
792 //! \group Signals emitted on room transitions
793 //!
794 //! Note: Rooms in Invite state are always stored separately from
795 //! rooms in Join/Leave state, because of special treatment of
796 //! invite_state in Matrix CS API (see The Spec on /sync for details).
797 //! Therefore, objects below are: r - room in Join/Leave state;
798 //! i - room in Invite state
799 //!
800 //! 1. none -> Invite: newRoom(r), invitedRoom(r,nullptr)
801 //! 2. none -> Join: newRoom(r), joinedRoom(r,nullptr)
802 //! 3. none -> Leave: newRoom(r), leftRoom(r,nullptr)
803 //! 4. Invite -> Join:
804 //! newRoom(r), joinedRoom(r,i), aboutToDeleteRoom(i)
805 //! 4a. Leave and Invite -> Join:
806 //! joinedRoom(r,i), aboutToDeleteRoom(i)
807 //! 5. Invite -> Leave:
808 //! newRoom(r), leftRoom(r,i), aboutToDeleteRoom(i)
809 //! 5a. Leave and Invite -> Leave:
810 //! leftRoom(r,i), aboutToDeleteRoom(i)
811 //! 6. Join -> Leave: leftRoom(r)
812 //! 7. Leave -> Invite: newRoom(i), invitedRoom(i,r)
813 //! 8. Leave -> Join: joinedRoom(r)
814 //! The following transitions are only possible via forgetRoom()
815 //! so far; if a room gets forgotten externally, sync won't tell
816 //! about it:
817 //! 9. any -> none: as any -> Leave, then aboutToDeleteRoom(r)
818
819 //! A new room object has been created
820 void newRoom(Quotient::Room* room);
821
822 //! \brief A room invitation is seen for the first time
823 //!
824 //! If the same room is in Left state, it's passed in prev. Beware
825 //! that initial sync will trigger this signal for all rooms in
826 //! Invite state.
827 void invitedRoom(Quotient::Room* room, Quotient::Room* prev);
828
829 //! \brief A joined room is seen for the first time
830 //!
831 //! It's not the same as receiving a room in "join" section of sync
832 //! response (rooms will be there even after joining); it's also
833 //! not (exactly) the same as actual joining action of a user (all
834 //! rooms coming in initial sync will trigger this signal too). If
835 //! this room was in Invite state before, the respective object is
836 //! passed in prev (and it will be deleted shortly afterwards).
837 void joinedRoom(Quotient::Room* room, Quotient::Room* prev);
838
839 //! \brief A room has just been left
840 //!
841 //! If this room has been in Invite state (as in case of rejecting
842 //! an invitation), the respective object will be passed in prev
843 //! (and will be deleted shortly afterwards). Note that, similar
844 //! to invitedRoom and joinedRoom, this signal is triggered for all
845 //! Left rooms upon initial sync (not only those that were left
846 //! right before the sync).
847 void leftRoom(Quotient::Room* room, Quotient::Room* prev);
848
849 //! The room object is about to be deleted
850 void aboutToDeleteRoom(Quotient::Room* room);
851
852 //! \brief The room has just been created by createRoom or requestDirectChat
853 //!
854 //! This signal is not emitted in usual room state transitions,
855 //! only as an outcome of room creation operations invoked by
856 //! the client.
857 //! \note requestDirectChat doesn't necessarily create a new chat;
858 //! directChatAvailable() is more appropriate if you need to obtain
859 //! a direct chat room after requestDirectChat().
860 void createdRoom(Quotient::Room* room);
861
862 //! \brief The first sync for the room has been completed
863 //!
864 //! This signal is emitted after the room has been synced the first
865 //! time. This is the right signal to connect to if you need to
866 //! access the room state (name, aliases, members); state transition
867 //! signals (newRoom, joinedRoom etc.) come earlier, when the room
868 //! has just been created.
869 void loadedRoomState(Quotient::Room* room);
870
871 //! Account data (except direct chats) have changed
872 void accountDataChanged(QString type);
873
874 //! \brief The direct chat room is ready for using
875 //!
876 //! This signal is emitted upon any successful outcome from
877 //! requestDirectChat.
878 void directChatAvailable(Quotient::Room* directChat);
879
880 //! \brief The list of direct chats has changed
881 //!
882 //! This signal is emitted every time when the mapping of users
883 //! to direct chat rooms is changed (because of either local updates
884 //! or a different list arrived from the server).
885 void directChatsListChanged(Quotient::DirectChatsMap additions,
886 Quotient::DirectChatsMap removals);
887
888 void ignoredUsersListChanged(Quotient::IgnoredUsersList additions,
889 Quotient::IgnoredUsersList removals);
890
891 void cacheStateChanged();
892 void lazyLoadingChanged();
893 void turnServersChanged(const QJsonObject& servers);
894 void devicesListLoaded();
895
896 //! Encryption has been enabled or disabled
897 void encryptionChanged(bool enabled);
898
899#ifdef Quotient_E2EE_ENABLED
900 void newKeyVerificationSession(Quotient::KeyVerificationSession* session);
901 void keyVerificationStateChanged(
902 const Quotient::KeyVerificationSession* session,
903 Quotient::KeyVerificationSession::State state);
904 void sessionVerified(const QString& userId, const QString& deviceId);
905 bool finishedQueryingKeys();
906#endif
907
908protected:
909 //! Access the underlying ConnectionData class
910 const ConnectionData* connectionData() const;
911
912 //! \brief Get a Room object for the given id in the given state
913 //!
914 //! Use this method when you need a Room object in the local list
915 //! of rooms, with the given state. Note that this does not interact
916 //! with the server; in particular, does not automatically create
917 //! rooms on the server. This call performs necessary join state
918 //! transitions; e.g., if it finds a room in Invite but
919 //! `joinState == JoinState::Join` then the Invite room object
920 //! will be deleted and a new room object with Join state created.
921 //! In contrast, switching between Join and Leave happens within
922 //! the same object.
923 //! \param roomId room id (not alias!)
924 //! \param joinState desired (target) join state of the room; if
925 //! omitted, any state will be found and return unchanged, or a
926 //! new Join room created.
927 //! \return a pointer to a Room object with the specified id and the
928 //! specified state; nullptr if roomId is empty or if roomFactory()
929 //! failed to create a Room object.
930 Room* provideRoom(const QString& roomId,
931 Omittable<JoinState> joinState = none);
932
933 //! Process sync data from a successful sync request
934 void onSyncSuccess(SyncData&& data, bool fromCache = false);
935
936protected Q_SLOTS:
937 void syncLoopIteration();
938
939private:
940 class Private;
941 ImplPtr<Private> d;
942
943 static room_factory_t _roomFactory;
944 static user_factory_t _userFactory;
945};
946} // namespace Quotient
947Q_DECLARE_METATYPE(Quotient::DirectChatsMap)
948Q_DECLARE_METATYPE(Quotient::IgnoredUsersList)
949