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