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