Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include "scheduled_executor.h"
20 : #include "jamidht/account_manager.h"
21 : #include "jamidht/conversation.h"
22 : #include "jamidht/conversationrepository.h"
23 : #include "jamidht/jami_contact.h"
24 :
25 : #include <mutex>
26 : #include <msgpack.hpp>
27 :
28 : namespace jami {
29 : static constexpr const char MIME_TYPE_INVITE[] {"application/invite"};
30 : static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"};
31 :
32 : class SIPCall;
33 :
34 : struct SyncMsg
35 : {
36 : DeviceSync ds;
37 : std::map<std::string, ConvInfo> c;
38 : std::map<std::string, ConversationRequest> cr;
39 : // p is conversation's preferences. It's not stored in c, as
40 : // we can update the preferences without touching any confInfo.
41 : std::map<std::string, std::map<std::string, std::string>> p;
42 : // Last displayed messages [[deprecated]]
43 : std::map<std::string, std::map<std::string, std::string>> ld;
44 : // Read & fetched status
45 : /*
46 : * {{ convId,
47 : * { memberUri,, {
48 : * {"fetch", "commitId"},
49 : * {"fetched_ts", "timestamp"},
50 : * {"read", "commitId"},
51 : * {"read_ts", "timestamp"}
52 : * }
53 : * }}
54 : */
55 : std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> ms;
56 140 : MSGPACK_DEFINE(ds, c, cr, p, ld, ms)
57 : };
58 :
59 : using ChannelCb = std::function<bool(const std::shared_ptr<dhtnet::ChannelSocket>&)>;
60 : using NeedSocketCb
61 : = std::function<void(const std::string&, const std::string&, ChannelCb&&, const std::string&)>;
62 : using SengMsgCb = std::function<
63 : uint64_t(const std::string&, const DeviceId&, std::map<std::string, std::string>, uint64_t)>;
64 : using NeedsSyncingCb = std::function<void(std::shared_ptr<SyncMsg>&&)>;
65 : using OneToOneRecvCb = std::function<void(const std::string&, const std::string&)>;
66 :
67 : class ConversationModule
68 : {
69 : public:
70 : ConversationModule(std::shared_ptr<JamiAccount> account,
71 : std::shared_ptr<AccountManager> accountManager,
72 : NeedsSyncingCb&& needsSyncingCb,
73 : SengMsgCb&& sendMsgCb,
74 : NeedSocketCb&& onNeedSocket,
75 : NeedSocketCb&& onNeedSwarmSocket,
76 : OneToOneRecvCb&& oneToOneRecvCb,
77 : bool autoLoadConversations = true);
78 308 : ~ConversationModule() = default;
79 :
80 : void setAccountManager(std::shared_ptr<AccountManager> accountManager);
81 :
82 : /**
83 : * Refresh information about conversations
84 : */
85 : void loadConversations();
86 :
87 : void loadSingleConversation(const std::string& convId);
88 :
89 : #ifdef LIBJAMI_TESTABLE
90 : void onBootstrapStatus(const std::function<void(std::string, Conversation::BootstrapStatus)>& cb);
91 : #endif
92 :
93 : void monitor();
94 :
95 : /**
96 : * Bootstrap swarm managers to other peers
97 : */
98 : void bootstrap(const std::string& convId = "");
99 :
100 : /**
101 : * Clear not removed fetch
102 : */
103 : void clearPendingFetch();
104 :
105 : /**
106 : * Reload requests from file
107 : */
108 : void reloadRequests();
109 :
110 : /**
111 : * Return all conversation's id (including syncing ones)
112 : */
113 : std::vector<std::string> getConversations() const;
114 :
115 : /**
116 : * Get related conversation with member
117 : * @param uri The member to search for
118 : * @return the conversation id if found else empty
119 : */
120 : std::string getOneToOneConversation(const std::string& uri) const noexcept;
121 :
122 : /**
123 : * Replace linked conversation in contact's details
124 : * @param uri Of the contact
125 : * @param oldConv Current conversation
126 : * @param newConv
127 : * @return if replaced
128 : */
129 : bool updateConvForContact(const std::string& uri,
130 : const std::string& oldConv,
131 : const std::string& newConv);
132 :
133 : /**
134 : * Return conversation's requests
135 : */
136 : std::vector<std::map<std::string, std::string>> getConversationRequests() const;
137 :
138 : /**
139 : * Called when detecting a new trust request with linked one to one
140 : * @param uri Sender's URI
141 : * @param conversationId Related conversation's id
142 : * @param payload VCard
143 : * @param received Received time
144 : */
145 : void onTrustRequest(const std::string& uri,
146 : const std::string& conversationId,
147 : const std::vector<uint8_t>& payload,
148 : time_t received);
149 :
150 : /**
151 : * Called when receiving a new conversation's request
152 : * @param from Sender
153 : * @param value Conversation's request
154 : */
155 : void onConversationRequest(const std::string& from, const Json::Value& value);
156 :
157 : /**
158 : * Retrieve author of a conversation request
159 : * @param convId Conversation's id
160 : * @return the author of the conversation request
161 : */
162 : std::string peerFromConversationRequest(const std::string& convId) const;
163 :
164 : /**
165 : * Called when a peer needs an invite for a conversation (generally after that they received
166 : * a commit notification for a conversation they don't have yet)
167 : * @param from
168 : * @param conversationId
169 : */
170 : void onNeedConversationRequest(const std::string& from, const std::string& conversationId);
171 :
172 : /**
173 : * Accept a conversation's request
174 : * @param convId
175 : * @param deviceId If a trust request is accepted from a device (can help to sync)
176 : */
177 : void acceptConversationRequest(const std::string& conversationId,
178 : const std::string& deviceId = "");
179 :
180 : /**
181 : * Decline a conversation's request
182 : * @param convId
183 : */
184 : void declineConversationRequest(const std::string& conversationId);
185 :
186 : /**
187 : * Clone conversation from a member
188 : * @note used to clone an old conversation after deleting/re-adding a contact
189 : * @param conversationId
190 : * @param uri
191 : * @param oldConvId
192 : */
193 : void cloneConversationFrom(const std::string& conversationId,
194 : const std::string& uri,
195 : const std::string& oldConvId = "");
196 :
197 : /**
198 : * Starts a new conversation
199 : * @param mode Wanted mode
200 : * @param otherMember If needed (one to one)
201 : * @return conversation's id
202 : */
203 : std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY,
204 : const dht::InfoHash& otherMember = {});
205 :
206 : // Message send/load
207 : void sendMessage(const std::string& conversationId,
208 : Json::Value&& value,
209 : const std::string& replyTo = "",
210 : bool announce = true,
211 : OnCommitCb&& onCommit = {},
212 : OnDoneCb&& cb = {});
213 :
214 : void sendMessage(const std::string& conversationId,
215 : std::string message,
216 : const std::string& replyTo = "",
217 : const std::string& type = "text/plain",
218 : bool announce = true,
219 : OnCommitCb&& onCommit = {},
220 : OnDoneCb&& cb = {});
221 :
222 : void editMessage(const std::string& conversationId,
223 : const std::string& newBody,
224 : const std::string& editedId);
225 : void reactToMessage(const std::string& conversationId,
226 : const std::string& newBody,
227 : const std::string& reactToId);
228 :
229 : /**
230 : * Add to the related conversation the call history message
231 : * @param uri Peer number
232 : * @param duration_ms The call duration in ms
233 : * @param reason
234 : */
235 : void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms, const std::string& reason);
236 :
237 : // Received that a peer displayed a message
238 : bool onMessageDisplayed(const std::string& peer,
239 : const std::string& conversationId,
240 : const std::string& interactionId);
241 : std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> convMessageStatus() const;
242 :
243 : /**
244 : * Load conversation's messages
245 : * @param conversationId Conversation to load
246 : * @param fromMessage
247 : * @param n Max interactions to load
248 : * @return id of the operation
249 : */
250 : uint32_t loadConversationMessages(const std::string& conversationId,
251 : const std::string& fromMessage = "",
252 : size_t n = 0);
253 : uint32_t loadConversation(const std::string& conversationId,
254 : const std::string& fromMessage = "",
255 : size_t n = 0);
256 : uint32_t loadConversationUntil(const std::string& conversationId,
257 : const std::string& fromMessage,
258 : const std::string& to);
259 : uint32_t loadSwarmUntil(const std::string& conversationId,
260 : const std::string& fromMessage,
261 : const std::string& toMessage);
262 : /**
263 : * Clear loaded interactions
264 : * @param conversationId
265 : */
266 : void clearCache(const std::string& conversationId);
267 :
268 : // File transfer
269 : /**
270 : * Returns related transfer manager
271 : * @param id Conversation's id
272 : * @return nullptr if not found, else the manager
273 : */
274 : std::shared_ptr<TransferManager> dataTransfer(const std::string& id) const;
275 :
276 : /**
277 : * Choose if we can accept channel request
278 : * @param member Member to check
279 : * @param fileId File transfer to check (needs to be waiting)
280 : * @param verifyShaSum For debug only
281 : * @return if we accept the channel request
282 : */
283 : bool onFileChannelRequest(const std::string& conversationId,
284 : const std::string& member,
285 : const std::string& fileId,
286 : bool verifyShaSum = true) const;
287 :
288 : /**
289 : * Ask conversation's members to send a file to this device
290 : * @param conversationId Related conversation
291 : * @param interactionId Related interaction
292 : * @param fileId Related fileId
293 : * @param path where to download the file
294 : */
295 : bool downloadFile(const std::string& conversationId,
296 : const std::string& interactionId,
297 : const std::string& fileId,
298 : const std::string& path,
299 : size_t start = 0,
300 : size_t end = 0);
301 :
302 : // Sync
303 : /**
304 : * Sync conversations with detected peer
305 : */
306 : void syncConversations(const std::string& peer, const std::string& deviceId);
307 :
308 : /**
309 : * Detect new conversations and request from other devices
310 : * @param msg Received data
311 : * @param peerId Sender
312 : * @param deviceId
313 : */
314 : void onSyncData(const SyncMsg& msg, const std::string& peerId, const std::string& deviceId);
315 :
316 : /**
317 : * Check if we need to share infos with a contact
318 : * @param memberUri
319 : * @param deviceId
320 : */
321 : bool needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const;
322 :
323 : /**
324 : * Notify that a peer fetched a commit
325 : * @note: this definitely remove the repository when needed (when we left and someone fetched
326 : * the information)
327 : * @param conversationId Related conv
328 : * @param deviceId Device who synced
329 : * @param commit HEAD synced
330 : */
331 : void setFetched(const std::string& conversationId,
332 : const std::string& deviceId,
333 : const std::string& commit);
334 :
335 : /**
336 : * Launch fetch on new commit
337 : * @param peer Who sent the notification
338 : * @param deviceId Who sent the notification
339 : * @param conversationId Related conversation
340 : * @param commitId Commit to retrieve
341 : */
342 : void fetchNewCommits(const std::string& peer,
343 : const std::string& deviceId,
344 : const std::string& conversationId,
345 : const std::string& commitId);
346 :
347 : // Conversation's member
348 : /**
349 : * Adds a new member to a conversation (this will triggers a member event + new message on success)
350 : * @param conversationId
351 : * @param contactUri
352 : * @param sendRequest If we need to inform the peer (used for tests)
353 : */
354 : void addConversationMember(const std::string& conversationId,
355 : const dht::InfoHash& contactUri,
356 : bool sendRequest = true);
357 : /**
358 : * Remove a member from a conversation (this will trigger a member event + new message on success)
359 : * @param conversationId
360 : * @param contactUri
361 : * @param isDevice
362 : */
363 : void removeConversationMember(const std::string& conversationId,
364 : const dht::InfoHash& contactUri,
365 : bool isDevice = false);
366 : /**
367 : * Get members
368 : * @param conversationId
369 : * @param includeBanned
370 : * @return a map of members with their role and details
371 : */
372 : std::vector<std::map<std::string, std::string>> getConversationMembers(
373 : const std::string& conversationId, bool includeBanned = false) const;
374 : /**
375 : * Retrieve the number of interactions from interactionId to HEAD
376 : * @param convId
377 : * @param interactionId "" for getting the whole history
378 : * @param authorUri Stop when detect author
379 : * @return number of interactions since interactionId
380 : */
381 : uint32_t countInteractions(const std::string& convId,
382 : const std::string& toId,
383 : const std::string& fromId,
384 : const std::string& authorUri) const;
385 :
386 : /**
387 : * Search in conversations via a filter
388 : * @param req Id of the request
389 : * @param convId Leave empty to search in all conversation, else add the conversation's id
390 : * @param filter Parameters for the search
391 : * @note triggers messagesFound
392 : */
393 : void search(uint32_t req, const std::string& convId, const Filter& filter) const;
394 :
395 : // Conversation's infos management
396 : /**
397 : * Update metadatas from conversations (like title, avatar, etc)
398 : * @param conversationId
399 : * @param infos
400 : * @param sync If we need to sync with others (used for tests)
401 : */
402 : void updateConversationInfos(const std::string& conversationId,
403 : const std::map<std::string, std::string>& infos,
404 : bool sync = true);
405 : std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const;
406 : /**
407 : * Update user's preferences (like color, notifications, etc) to be synced across devices
408 : * @param conversationId
409 : * @param preferences
410 : */
411 : void setConversationPreferences(const std::string& conversationId,
412 : const std::map<std::string, std::string>& prefs);
413 : std::map<std::string, std::string> getConversationPreferences(const std::string& conversationId,
414 : bool includeCreated = false) const;
415 : /**
416 : * Retrieve all conversation preferences to sync with other devices
417 : */
418 : std::map<std::string, std::map<std::string, std::string>> convPreferences() const;
419 : // Get the map into a VCard format for storing
420 : std::vector<uint8_t> conversationVCard(const std::string& conversationId) const;
421 :
422 : /**
423 : * Return if a device or member is banned from a conversation
424 : * @param convId
425 : * @param uri
426 : */
427 : bool isBanned(const std::string& convId, const std::string& uri) const;
428 :
429 : // Remove swarm
430 : /**
431 : * Remove one to one conversations related to a contact
432 : * @param uri Of the contact
433 : * @param ban If banned
434 : */
435 : void removeContact(const std::string& uri, bool ban);
436 :
437 : /**
438 : * Remove a conversation, but not the contact
439 : * @param conversationId
440 : * @return if successfully removed
441 : */
442 : bool removeConversation(const std::string& conversationId);
443 : void initReplay(const std::string& oldConvId, const std::string& newConvId);
444 : /**
445 : * Check if we're hosting a specific conference
446 : * @param conversationId (empty to search all conv)
447 : * @param confId
448 : * @return true if hosting this conference
449 : */
450 : bool isHosting(const std::string& conversationId, const std::string& confId) const;
451 : /**
452 : * Return active calls
453 : * @param convId Which conversation to choose
454 : * @return {{"id":id}, {"uri":uri}, {"device":device}}
455 : */
456 : std::vector<std::map<std::string, std::string>> getActiveCalls(
457 : const std::string& conversationId) const;
458 : /**
459 : * Call the conversation
460 : * @param url Url to call (swarm:conversation or swarm:conv/account/device/conf to join)
461 : * @param mediaList The media list
462 : * @param cb Callback to pass which device to call (called in the same thread)
463 : * @return call if a call is started, else nullptr
464 : */
465 : std::shared_ptr<SIPCall> call(const std::string& url,
466 : const std::vector<libjami::MediaMap>& mediaList,
467 : std::function<void(const std::string&, const DeviceId&, const std::shared_ptr<SIPCall>&)>&& cb);
468 : void hostConference(const std::string& conversationId,
469 : const std::string& confId,
470 : const std::string& callId,
471 : const std::vector<libjami::MediaMap>& mediaList = {});
472 :
473 : // The following methods modify what is stored on the disk
474 : static void saveConvInfos(const std::string& accountId,
475 : const std::map<std::string, ConvInfo>& conversations);
476 : static void saveConvInfosToPath(const std::filesystem::path& path,
477 : const std::map<std::string, ConvInfo>& conversations);
478 : static void saveConvRequests(
479 : const std::string& accountId,
480 : const std::map<std::string, ConversationRequest>& conversationsRequests);
481 : static void saveConvRequestsToPath(
482 : const std::filesystem::path& path,
483 : const std::map<std::string, ConversationRequest>& conversationsRequests);
484 :
485 : static std::map<std::string, ConvInfo> convInfos(const std::string& accountId);
486 : static std::map<std::string, ConvInfo> convInfosFromPath(const std::filesystem::path& path);
487 : static std::map<std::string, ConversationRequest> convRequests(const std::string& accountId);
488 : static std::map<std::string, ConversationRequest> convRequestsFromPath(
489 : const std::filesystem::path& path);
490 : void addConvInfo(const ConvInfo& info);
491 :
492 : /**
493 : * Get a conversation
494 : * @param convId
495 : */
496 : std::shared_ptr<Conversation> getConversation(const std::string& convId);
497 : /**
498 : * Return current git socket used for a conversation
499 : * @param deviceId Related device
500 : * @param conversationId Related conversation
501 : * @return the related socket
502 : */
503 : std::shared_ptr<dhtnet::ChannelSocket> gitSocket(std::string_view deviceId,
504 : std::string_view convId) const;
505 : void removeGitSocket(std::string_view deviceId, std::string_view convId);
506 : void addGitSocket(std::string_view deviceId,
507 : std::string_view convId,
508 : const std::shared_ptr<dhtnet::ChannelSocket>& channel);
509 : /**
510 : * Clear all connection (swarm channels)
511 : */
512 : void shutdownConnections();
513 : /**
514 : * Add a swarm connection
515 : * @param conversationId
516 : * @param socket
517 : */
518 : void addSwarmChannel(const std::string& conversationId,
519 : std::shared_ptr<dhtnet::ChannelSocket> socket);
520 : /**
521 : * Triggers a bucket maintainance for DRTs
522 : */
523 : void connectivityChanged();
524 :
525 : /**
526 : * Get Typers object for a conversation
527 : * @param convId
528 : * @return the Typer object
529 : */
530 : std::shared_ptr<Typers> getTypers(const std::string& convId);
531 :
532 : private:
533 : class Impl;
534 : std::shared_ptr<Impl> pimpl_;
535 : };
536 :
537 : } // namespace jami
|