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