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