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 :
18 : #pragma once
19 :
20 : #include "jamidht/commit_message.h"
21 : #include "jamidht/conversationrepository.h"
22 : #include "jamidht/gitsocket.h"
23 : #include "conversationrepository.h"
24 : #include "swarm/swarm_protocol.h"
25 : #include "jami/conversation_interface.h"
26 : #include "jamidht/typers.h"
27 : #include "string_utils.h"
28 : #include "timestamp.h"
29 :
30 : #include <json/json.h>
31 : #include <msgpack.hpp>
32 :
33 : #include <chrono>
34 : #include <functional>
35 : #include <string>
36 : #include <vector>
37 : #include <map>
38 : #include <memory>
39 : #include <set>
40 :
41 : #include <asio.hpp>
42 :
43 : namespace dhtnet {
44 : class ChannelSocket;
45 : } // namespace dhtnet
46 :
47 : namespace jami {
48 :
49 : namespace ConversationMapKeys {
50 : static constexpr const char* ID {"id"};
51 : static constexpr const char* CREATED {"created"};
52 : static constexpr const char* REMOVED {"removed"};
53 : static constexpr const char* ERASED {"erased"};
54 : static constexpr const char* MEMBERS {"members"};
55 : static constexpr const char* LAST_DISPLAYED {"lastDisplayed"};
56 : static constexpr const char* RECEIVED {"received"};
57 : static constexpr const char* DECLINED {"declined"};
58 : static constexpr const char* FROM {"from"};
59 : static constexpr const char* CONVERSATIONID {"conversationId"};
60 : static constexpr const char* METADATAS {"metadatas"};
61 : static constexpr const char* MODE {"mode"};
62 : // Millisecond-resolution variants. Legacy keys above keep carrying seconds
63 : // so that older devices (which ignore unknown keys) remain compatible.
64 : static constexpr const char* CREATED_MS {"createdMs"};
65 : static constexpr const char* REMOVED_MS {"removedMs"};
66 : static constexpr const char* ERASED_MS {"erasedMs"};
67 : static constexpr const char* RECEIVED_MS {"receivedMs"};
68 : static constexpr const char* DECLINED_MS {"declinedMs"};
69 : static constexpr const char* INVITED {"invited"};
70 : } // namespace ConversationMapKeys
71 :
72 : namespace ConversationDirectories {
73 : static constexpr std::string_view PREFERENCES {"preferences"};
74 : static constexpr std::string_view STATUS {"status"};
75 : static constexpr std::string_view SENDING {"sending"};
76 : static constexpr std::string_view FETCHED {"fetched"};
77 : static constexpr std::string_view ACTIVE_CALLS {"activeCalls"};
78 : static constexpr std::string_view HOSTED_CALLS {"hostedCalls"};
79 : static constexpr std::string_view CACHED {"cached"};
80 : static constexpr std::string_view MOBILE_NODES {"mobileNodes"};
81 : } // namespace ConversationDirectories
82 :
83 : namespace ConversationPreferences {
84 : static constexpr const char* HOST_CONFERENCES = "hostConferences";
85 : }
86 :
87 : struct MobileNodeTarget
88 : {
89 : NodeId device;
90 : std::string uri;
91 : };
92 :
93 : class JamiAccount;
94 : class ConversationRepository;
95 : class TransferManager;
96 : enum class ConversationMode;
97 :
98 : /**
99 : * A ConversationRequest is a request which corresponds to a trust request, but for conversations
100 : * It's signed by the sender and contains the members list, the conversationId, and the metadatas
101 : * such as the conversation's vcard, etc. (TODO determine)
102 : * Transmitted via the UDP DHT
103 : */
104 : struct ConversationRequest
105 : {
106 : std::string conversationId;
107 : std::string from;
108 : std::map<std::string, std::string> metadatas;
109 :
110 : TimePoint received {};
111 : TimePoint declined {};
112 :
113 232 : ConversationRequest() = default;
114 : ConversationRequest(const Json::Value& json);
115 :
116 : Json::Value toJson() const;
117 : std::map<std::string, std::string> toMap() const;
118 :
119 : bool operator==(const ConversationRequest& o) const
120 : {
121 : auto m = toMap();
122 : auto om = o.toMap();
123 : return m.size() == om.size() && std::equal(m.begin(), m.end(), om.begin());
124 : }
125 :
126 413 : bool isOneToOne() const
127 : {
128 : try {
129 1239 : return metadatas.at("mode") == "0";
130 41 : } catch (...) {
131 41 : }
132 41 : return true;
133 : }
134 :
135 146 : ConversationMode mode() const
136 : {
137 : try {
138 438 : return to_enum<ConversationMode>(metadatas.at("mode"));
139 30 : } catch (...) {
140 30 : }
141 30 : return ConversationMode::ONE_TO_ONE;
142 : }
143 :
144 : // Hand-written msgpack serialization (replaces MSGPACK_DEFINE_MAP) to emit
145 : // dual keys: legacy seconds (received/declined) + milliseconds (receivedMs/declinedMs).
146 : // Readers prefer the ms keys and fall back to seconds * 1000.
147 : template<typename Packer>
148 181 : void msgpack_pack(Packer& pk) const
149 : {
150 181 : int64_t receivedSec = toSecondsSinceEpoch(received);
151 181 : int64_t declinedSec = toSecondsSinceEpoch(declined);
152 181 : int64_t receivedMs = toMillisecondsSinceEpoch(received);
153 181 : int64_t declinedMs = toMillisecondsSinceEpoch(declined);
154 : msgpack::type::make_define_map(ConversationMapKeys::FROM,
155 181 : from,
156 : ConversationMapKeys::CONVERSATIONID,
157 181 : conversationId,
158 : ConversationMapKeys::METADATAS,
159 181 : metadatas,
160 : ConversationMapKeys::RECEIVED,
161 : receivedSec,
162 : ConversationMapKeys::DECLINED,
163 : declinedSec,
164 : ConversationMapKeys::RECEIVED_MS,
165 : receivedMs,
166 : ConversationMapKeys::DECLINED_MS,
167 : declinedMs)
168 181 : .msgpack_pack(pk);
169 181 : }
170 : void msgpack_unpack(const msgpack::object& o);
171 : void msgpack_object(msgpack::object* o, msgpack::zone& z) const;
172 : };
173 :
174 : struct ConvInfo
175 : {
176 : std::string id {};
177 : TimePoint created {};
178 : TimePoint removed {};
179 : TimePoint erased {};
180 : std::set<std::string> members;
181 : std::string lastDisplayed {};
182 : ConversationMode mode {0};
183 : // Last time we explicitly (re-)invited a given peer (via addConversationMember()/
184 : // sendTrustRequest()), keyed by peer URI.
185 : std::map<std::string, TimePoint> invited {};
186 :
187 670 : ConvInfo() = default;
188 176 : ConvInfo(const ConvInfo&) = default;
189 0 : ConvInfo(ConvInfo&&) = default;
190 471 : ConvInfo(const std::string& id)
191 471 : : id(id) {};
192 : explicit ConvInfo(const Json::Value& json);
193 :
194 13386 : bool isRemoved() const { return removed >= created; }
195 :
196 2743 : ConvInfo& operator=(const ConvInfo&) = default;
197 313 : ConvInfo& operator=(ConvInfo&&) = default;
198 :
199 : Json::Value toJson() const;
200 :
201 : // Hand-written msgpack serialization (replaces MSGPACK_DEFINE_MAP) to emit
202 : // dual keys: legacy seconds (created/removed/erased) + milliseconds
203 : // (createdMs/removedMs/erasedMs). Readers prefer the ms keys and fall back
204 : // to seconds * 1000.
205 : template<typename Packer>
206 3201 : void msgpack_pack(Packer& pk) const
207 : {
208 3201 : int64_t createdSec = toSecondsSinceEpoch(created);
209 3201 : int64_t removedSec = toSecondsSinceEpoch(removed);
210 3200 : int64_t erasedSec = toSecondsSinceEpoch(erased);
211 3201 : int64_t createdMs = toMillisecondsSinceEpoch(created);
212 3200 : int64_t removedMs = toMillisecondsSinceEpoch(removed);
213 3200 : int64_t erasedMs = toMillisecondsSinceEpoch(erased);
214 3200 : std::map<std::string, int64_t> invitedMs;
215 6779 : for (const auto& [uri, t] : invited)
216 3577 : invitedMs[uri] = toMillisecondsSinceEpoch(t);
217 : msgpack::type::make_define_map(ConversationMapKeys::ID,
218 3201 : id,
219 : ConversationMapKeys::CREATED,
220 : createdSec,
221 : ConversationMapKeys::REMOVED,
222 : removedSec,
223 : ConversationMapKeys::ERASED,
224 : erasedSec,
225 : ConversationMapKeys::MEMBERS,
226 3201 : members,
227 : ConversationMapKeys::LAST_DISPLAYED,
228 3201 : lastDisplayed,
229 : ConversationMapKeys::MODE,
230 3201 : mode,
231 : ConversationMapKeys::CREATED_MS,
232 : createdMs,
233 : ConversationMapKeys::REMOVED_MS,
234 : removedMs,
235 : ConversationMapKeys::ERASED_MS,
236 : erasedMs,
237 : ConversationMapKeys::INVITED,
238 : invitedMs)
239 3201 : .msgpack_pack(pk);
240 3201 : }
241 : void msgpack_unpack(const msgpack::object& o);
242 : void msgpack_object(msgpack::object* o, msgpack::zone& z) const;
243 : };
244 :
245 : using OnPullCb = std::function<void(bool fetchOk)>;
246 : using OnLoadMessages = std::function<void(std::vector<libjami::SwarmMessage>&& messages)>;
247 : using OnCommitCb = std::function<void(const std::string&)>;
248 : using OnDoneCb = std::function<void(bool, const std::string&)>;
249 : using OnMembersChanged = std::function<void(const std::set<std::string>&)>;
250 : using DeviceId = dht::PkId;
251 : using GitSocketList = std::map<DeviceId, GitSocket>;
252 : using ChannelCb = std::function<bool(const std::shared_ptr<dhtnet::ChannelSocket>&)>;
253 : using NeedSocketCb
254 : = std::function<void(const std::string&, const std::string&, ChannelCb&&, const std::string&, bool noNewSocket)>;
255 :
256 : class Conversation : public std::enable_shared_from_this<Conversation>
257 : {
258 : public:
259 : Conversation(const std::shared_ptr<JamiAccount>& account,
260 : ConversationMode mode,
261 : const std::string& otherMember = "");
262 : Conversation(const std::shared_ptr<JamiAccount>& account, const std::string& conversationId = "");
263 : Conversation(const std::shared_ptr<JamiAccount>& account,
264 : const std::string& remoteDevice,
265 : const std::string& conversationId);
266 : ~Conversation();
267 :
268 : /**
269 : * Print the state of the DRT linked to the conversation
270 : */
271 : void monitor();
272 :
273 : #ifdef LIBJAMI_TEST
274 : enum class BootstrapStatus { FAILED, FALLBACK, SUCCESS };
275 : /**
276 : * Used by the tests to get whenever the DRT is connected/disconnected
277 : */
278 : void onBootstrapStatus(const std::function<void(std::string, BootstrapStatus)>& cb);
279 :
280 : std::vector<libjami::SwarmMessage> loadMessagesSync(const LogOptions& options);
281 : void announce(const std::vector<std::map<std::string, std::string>>& commits, bool commitFromSelf = false);
282 : void announce(const std::string& commitId, bool commitFromSelf = false);
283 : #endif
284 :
285 : /**
286 : * Bootstrap swarm manager to other peers
287 : * @param onBootstrapped Callback called when connection is established successfully
288 : * @param knownDevices Optional list of live devices to seed the DRT with.
289 : * Normally empty: candidates are fed by the per-device
290 : * presence monitoring through addKnownDevices().
291 : */
292 : void bootstrap(std::function<void()> onBootstrapped, const std::vector<DeviceId>& knownDevices = {});
293 :
294 : /**
295 : * Add known devices to the swarm manager
296 : * @param devices
297 : * @param memberUri
298 : */
299 : void addKnownDevices(const std::vector<DeviceId>& devices, const std::string& memberUri);
300 :
301 : /**
302 : * Proactively connect a device in the swarm, bypassing DRT bucket checks.
303 : * Used when a TCP link to the device already exists.
304 : * @param deviceId
305 : */
306 : void connectNode(const DeviceId& deviceId);
307 :
308 : /**
309 : * Refresh active calls.
310 : * @note: If the host crash during a call, when initializing, we need to update
311 : * and commit all the crashed calls
312 : * @return Commits added
313 : */
314 : std::vector<std::string> commitsEndedCalls();
315 :
316 : void onMembersChanged(OnMembersChanged&& cb);
317 :
318 : /**
319 : * Set the callback that will be called whenever a new socket will be needed
320 : * @param cb
321 : */
322 : void onNeedSocket(NeedSocketCb cb);
323 : /**
324 : * Add swarm connection to the DRT
325 : * @param channel Related channel
326 : */
327 : void addSwarmChannel(std::shared_ptr<dhtnet::ChannelSocket> channel);
328 :
329 : /**
330 : * Get conversation's id
331 : * @return conversation Id
332 : */
333 : std::string id() const;
334 :
335 : // Member management
336 : /**
337 : * Add conversation member
338 : * @param uri Member to add
339 : * @param cb On done cb
340 : */
341 : void addMember(const std::string& contactUri, const OnDoneCb& cb = {});
342 : void removeMember(const std::string& contactUri, bool isDevice, const OnDoneCb& cb = {});
343 : /**
344 : * @param includeInvited If we want invited members
345 : * @param includeLeft If we want left members
346 : * @param includeBanned If we want banned members
347 : * @return a vector of member details:
348 : * {
349 : * "uri":"xxx",
350 : * "role":"member/admin/invited",
351 : * "lastDisplayed":"id"
352 : * ...
353 : * }
354 : */
355 : std::vector<std::map<std::string, std::string>> getMembers(bool includeInvited = false,
356 : bool includeLeft = false,
357 : bool includeBanned = false) const;
358 :
359 : /**
360 : * @param filter If we want to remove one member
361 : * @param filteredRoles If we want to ignore some roles
362 : * @return members' uris
363 : */
364 : std::set<std::string> memberUris(std::string_view filter = {},
365 : const std::set<MemberRole>& filteredRoles = {MemberRole::INVITED,
366 : MemberRole::LEFT,
367 : MemberRole::BANNED}) const;
368 :
369 : std::vector<std::map<std::string, std::string>> getTrackedMembers() const;
370 :
371 : /**
372 : * Get peers to sync with. This is mostly managed by the DRT
373 : * @return all connected nodes and nodes with a git socket
374 : */
375 : std::vector<NodeId> peersToSyncWith() const;
376 : /**
377 : * Get the mobile nodes this device is responsible for waking up
378 : * when a new message must be synced. Mobile nodes are not actively
379 : * connected to the DRT, so they are notified via a DHT message
380 : * (converted to a push notification by the proxy).
381 : * @return mobile nodes we are closer to than any connected node
382 : */
383 : std::vector<MobileNodeTarget> mobileNodesToNotify() const;
384 : /**
385 : * Check if we're at least connected to one node
386 : * @return if the DRT is connected
387 : */
388 : bool isBootstrapped() const;
389 : /**
390 : * Retrieve the uri from a deviceId
391 : * @note used by swarm manager (peersToSyncWith)
392 : * @param deviceId
393 : * @return corresponding issuer
394 : */
395 : std::string uriFromDevice(const std::string& deviceId) const;
396 :
397 : /**
398 : * The devices recorded in the repository, per member.
399 : * @return member uri -> that member's device ids
400 : */
401 : std::map<std::string, std::vector<DeviceId>> memberDevices() const;
402 :
403 : /**
404 : * Join a conversation
405 : * @return commit id to send
406 : */
407 : std::string join();
408 :
409 : /**
410 : * Test if an URI is a member
411 : * @param uri URI to test
412 : * @return true if uri is a member
413 : */
414 : bool isMember(const std::string& uri, bool includeInvited = false) const;
415 : bool isMemberBanned(const std::string& uri) const;
416 : bool isDeviceBanned(const std::string& deviceId) const;
417 :
418 : /**
419 : * Check if a device is authorized to clone or interact with the conversation.
420 : * Assumption: the deviceId must already be a confirmed device of the user.
421 : * @param uri URI of the user
422 : * @param deviceId Device id to check
423 : * @param includeInvited If true, consider invited members as authorized.
424 : */
425 : bool isPeerAuthorized(const std::string& uri, const std::string& deviceId, bool includeInvited = false) const;
426 :
427 : void createCommit(CommitMessage&& message, OnCommitCb&& onCommit = {}, OnDoneCb&& cb = {});
428 :
429 : /**
430 : * Get a range of messages
431 : * @param cb The callback when loaded
432 : * @param options The log options
433 : */
434 : void loadMessages(const OnLoadMessages& cb, const LogOptions& options);
435 : /**
436 : * For every loaded TEXT message that has no bodyOverwrite in pluginData, runs the plugin
437 : * ChatServicesManager transform to obtain one. If the plugin produces a bodyOverwrite,
438 : * stores it in pluginData and emits SwarmMessageUpdated for the affected message
439 : * (edition commits signal the original message; originals signal themselves).
440 : * No-op when no plugin chat handlers are registered.
441 : */
442 : void loadMissingBodyOverwrites() const;
443 : /**
444 : * Drops all bodyOverwrite entries from the loaded message history, then calls
445 : * loadMissingBodyOverwrites() to re-derive them from scratch via the plugin transform.
446 : * Use when plugin chat handlers are reloaded or their transform logic has changed.
447 : */
448 : void reloadBodyOverwriteMessages() const;
449 : /**
450 : * Stores or clears the plugin-provided bodyOverwrite for a single message.
451 : * An empty bodyOverwrite sets the entry to "" (marks the message as processed with no
452 : * overwrite, preventing reprocessing); a non-empty value replaces it.
453 : * If messageId refers to an edition commit, SwarmMessageUpdated is emitted for the
454 : * original message (the one clients display); otherwise it is emitted for messageId itself.
455 : * @param messageId Id of the message (original or edition commit) to update.
456 : * @param bodyOverwrite Plugin-transformed body text, or empty to clear the override.
457 : */
458 : void updateMessageBodyOverwrite(const std::string& messageId, const std::string& bodyOverwrite) const;
459 : /**
460 : * Removes all bodyOverwrite entries from every message in the loaded history and emits
461 : * SwarmMessageUpdated for each affected message so clients revert to the original
462 : * body. Edition commits are resolved to their original message before signalling.
463 : */
464 : void clearBodyOverwrites() const;
465 : /**
466 : * Clear all cached messages
467 : */
468 : void clearCache();
469 : /**
470 : * Check if a commit exists in the repository
471 : * @param commitId The commit id to check
472 : * @return true if the commit was found, false if not or if an error occurred
473 : */
474 : bool hasCommit(const std::string& commitId) const;
475 : /**
476 : * Retrieve one commit
477 : * @param commitId
478 : * @return The commit if found
479 : */
480 : std::optional<ConversationCommit> getCommit(const std::string& commitId) const;
481 : /**
482 : * List every collaborative document announced in this conversation, by scanning the
483 : * repository for COLLAB_DOC commits rather than the loaded message history. Lets a
484 : * client show the editable documents without first paging in the (possibly old)
485 : * announcing messages.
486 : * @return one map per COLLAB_DOC commit ("uri" = document id, "displayName",
487 : * "mimeType", "author", "timestamp"), newest first
488 : */
489 : std::vector<std::map<std::string, std::string>> collaborativeDocuments() const;
490 : /**
491 : * Get last commit id
492 : * @return last commit id
493 : */
494 : std::string lastCommitId() const;
495 :
496 : /**
497 : * Fetch and merge from peer
498 : * @param deviceId Peer device
499 : * @param cb On pulled callback
500 : * @param commitId Commit id that triggered this fetch
501 : * @return true if callback will be called later
502 : */
503 : bool pull(const std::string& deviceId, OnPullCb&& cb, std::string commitId = "");
504 : /**
505 : * Fetch new commits and re-ask for waiting files
506 : * @param member
507 : * @param deviceId
508 : * @param cb cf pull()
509 : * @param commitId cf pull()
510 : */
511 : void sync(const std::string& member, const std::string& deviceId, OnPullCb&& cb, std::string commitId = "");
512 :
513 : /**
514 : * Generate an invitation to send to new contacts
515 : * @param sent timestamp of the explicit action that caused this invite to be (re)sent
516 : * (e.g. addConversationMember()). Callers that merely relay/resend an
517 : * already-generated invite must pass the originally recorded timestamp.
518 : * @return the invite to send
519 : */
520 : std::map<std::string, std::string> generateInvitation(TimePoint sent) const;
521 :
522 : /**
523 : * Leave a conversation
524 : * @return commit id to send
525 : */
526 : std::string leave();
527 :
528 : /**
529 : * Set a conversation as removing (when loading convInfo and still not sync)
530 : * @todo: not a big fan to see this here. can be set in the constructor
531 : * cause it's used by jamiaccount when loading conversations
532 : */
533 : void setRemovingFlag();
534 :
535 : /**
536 : * Check if we are removing the conversation
537 : * @return true if left the room
538 : */
539 : bool isRemoving();
540 :
541 : /**
542 : * Erase all related datas
543 : */
544 : void erase();
545 :
546 : /**
547 : * Get conversation's mode
548 : * @return the mode
549 : */
550 : ConversationMode mode() const;
551 :
552 : /**
553 : * For a collaborative document (mode DOCUMENT), the id of the conversation
554 : * that announced it, read from the initial commit.
555 : * @return empty for any other mode
556 : */
557 : std::string parentConversationId() const;
558 :
559 : /**
560 : * For a collaborative document, the MIME type of what it holds, read from
561 : * the initial commit.
562 : * @return empty for any other mode
563 : */
564 : std::string documentMimeType() const;
565 :
566 : /**
567 : * For a collaborative document, every CRDT update persisted in the
568 : * repository, oldest first: the base64-encoded lines of the body of each
569 : * checkpoint commit, in the order they must be replayed.
570 : */
571 : std::vector<std::string> documentUpdates() const;
572 :
573 : /**
574 : * Same as documentUpdates() but only up to (and including) the given
575 : * commit, to rebuild the document as it was at that point.
576 : * @return std::nullopt when the commit is unknown, as opposed to an empty
577 : * document at a known commit
578 : */
579 : std::optional<std::vector<std::string>> documentUpdatesAt(const std::string& commitId) const;
580 :
581 : /**
582 : * For a collaborative document, its persisted history: one map per
583 : * checkpoint commit, newest first, with keys "id", "author", "device",
584 : * "timestamp" and "deltas" (the number of updates the checkpoint carries).
585 : * @param max stop after this many entries; 0 for no limit
586 : */
587 : std::vector<std::map<std::string, std::string>> documentHistory(size_t max) const;
588 :
589 : /**
590 : * For a collaborative document, store an attachment and announce the
591 : * commit to the swarm.
592 : * @return {attachmentId, commitId}; commitId is empty when the same
593 : * content was already attached (nothing new to announce) and both
594 : * are empty on failure
595 : */
596 : std::pair<std::string, std::string> addDocumentAttachment(const std::vector<uint8_t>& data);
597 :
598 : /**
599 : * For a collaborative document, read an attachment's content at HEAD.
600 : */
601 : std::vector<uint8_t> documentAttachment(const std::string& attachmentId) const;
602 :
603 : /**
604 : * For a collaborative document, list the attachment ids present at HEAD.
605 : */
606 : std::vector<std::string> documentAttachmentIds() const;
607 :
608 : /**
609 : * One to one util, get initial members
610 : * @return initial members
611 : */
612 : std::vector<std::string> getInitialMembers() const;
613 : bool isInitialMember(const std::string& uri) const;
614 :
615 : /**
616 : * Change repository's infos
617 : * @param map New infos (supported keys: title, description, avatar)
618 : * @param cb On commited
619 : */
620 : void updateInfos(const std::map<std::string, std::string>& map, const OnDoneCb& cb = {});
621 :
622 : /**
623 : * Change user's preferences
624 : * @param map New preferences
625 : */
626 : void updatePreferences(const std::map<std::string, std::string>& map);
627 :
628 : /**
629 : * Retrieve current infos (title, description, avatar, mode)
630 : * @return infos
631 : */
632 : std::map<std::string, std::string> infos() const;
633 : /**
634 : * Retrieve current preferences (color, notification, etc)
635 : * @param includeLastModified If we want to know when the preferences were modified
636 : * @return preferences
637 : */
638 : std::map<std::string, std::string> preferences(bool includeLastModified) const;
639 : std::vector<uint8_t> vCard() const;
640 :
641 : /////// File transfer
642 :
643 : /**
644 : * Access to transfer manager
645 : */
646 : std::shared_ptr<TransferManager> dataTransfer() const;
647 :
648 : /**
649 : * Choose if we can accept channel request
650 : * @param member member to check
651 : * @param fileId file transfer to check (needs to be waiting)
652 : * @param verifyShaSum for debug only
653 : * @return if we accept the channel request
654 : */
655 : bool onFileChannelRequest(const std::string& member,
656 : const std::string& fileId,
657 : std::filesystem::path& path,
658 : std::string& sha3sum) const;
659 : /**
660 : * Adds a file to the waiting list and ask members
661 : * @param interactionId Related interaction id
662 : * @param fileId Related id
663 : * @param path Destination
664 : * @param member Member if we know from who to pull file
665 : * @param deviceId Device if we know from who to pull file
666 : * @return id of the file
667 : */
668 : bool downloadFile(const std::string& interactionId,
669 : const std::string& fileId,
670 : const std::string& path,
671 : const std::string& member = "",
672 : const std::string& deviceId = "");
673 :
674 : /**
675 : * Reset fetched information
676 : */
677 : void clearFetched();
678 : /**
679 : * Store information about who fetch or not. This simplify sync (sync when a device without the
680 : * last fetch is detected)
681 : * @param deviceId
682 : * @param commitId
683 : */
684 : void hasFetched(const std::string& deviceId, const std::string& commitId);
685 :
686 : /**
687 : * Store last read commit (returned in getMembers)
688 : * @param uri Of the member
689 : * @param interactionId Last interaction displayed
690 : * @return if updated
691 : */
692 : bool setMessageDisplayed(const std::string& uri, const std::string& interactionId);
693 : /**
694 : * Retrieve last displayed and fetch status per member
695 : * @return A map with the following structure:
696 : * {uri, {
697 : * {"fetch", "commitId"},
698 : * {"fetched_ts", "timestamp"},
699 : * {"read", "commitId"},
700 : * {"read_ts", "timestamp"}
701 : * }
702 : * }
703 : */
704 : std::map<std::string, std::map<std::string, std::string>> messageStatus() const;
705 : /**
706 : * Update fetch/read status
707 : * @param messageStatus A map with the following structure:
708 : * {uri, {
709 : * {"fetch", "commitId"},
710 : * {"fetched_ts", "timestamp"},
711 : * {"read", "commitId"},
712 : * {"read_ts", "timestamp"}
713 : * }
714 : * }
715 : */
716 : void updateMessageStatus(const std::map<std::string, std::map<std::string, std::string>>& messageStatus);
717 : void onMessageStatusChanged(
718 : const std::function<void(const std::map<std::string, std::map<std::string, std::string>>&)>& cb);
719 : /**
720 : * Retrieve how many interactions there is from HEAD to interactionId
721 : * @param toId "" for getting the whole history
722 : * @param fromId "" => HEAD
723 : * @param authorURI author to stop counting
724 : * @return number of interactions since interactionId
725 : */
726 : uint32_t countInteractions(const std::string& toId,
727 : const std::string& fromId = "",
728 : const std::string& authorUri = "") const;
729 : /**
730 : * Search in the conversation via a filter
731 : * @param req Id of the request
732 : * @param filter Parameters for the search
733 : * @param flag To check when search is finished
734 : * @note triggers messagesFound
735 : */
736 : void search(uint32_t req, const Filter& filter, const std::shared_ptr<std::atomic_int>& flag) const;
737 : /**
738 : * Host a conference in the conversation
739 : * @note the message must have "confId"
740 : * @note Update hostedCalls_ and commit in the conversation
741 : * @param message message to commit
742 : * @param cb callback triggered when committed
743 : */
744 : void hostConference(CommitMessage&& message, OnDoneCb&& cb = {});
745 : /**
746 : * Announce the end of a call
747 : * @note the message must have "confId"
748 : * @note called when conference is finished
749 : * @param message message to commit
750 : * @param cb callback triggered when committed
751 : */
752 : void removeActiveConference(CommitMessage&& message, OnDoneCb&& cb = {});
753 : /**
754 : * Check if we're currently hosting this conference
755 : * @param confId
756 : * @return true if hosting
757 : */
758 : bool isHosting(const std::string& confId) const;
759 : /**
760 : * Return current detected calls
761 : * @return a vector of map with the following keys: "id", "uri", "device"
762 : */
763 : std::vector<std::map<std::string, std::string>> currentCalls() const;
764 :
765 : /**
766 : * Git operations will need a ChannelSocket for cloning/fetching commits
767 : * Because libgit2 is a C library, we store the pointer in the corresponding conversation
768 : * and the GitTransport will inject to libgit2 whenever needed
769 : */
770 : std::shared_ptr<dhtnet::ChannelSocket> gitSocket(const DeviceId& deviceId) const;
771 : void addGitSocket(const DeviceId& deviceId, const std::shared_ptr<dhtnet::ChannelSocket>& socket);
772 : /** If @p expected is set, only remove the socket if it is still the registered one. */
773 : void removeGitSocket(const DeviceId& deviceId, const std::shared_ptr<dhtnet::ChannelSocket>& expected = {});
774 :
775 : /**
776 : * Stop SwarmManager, bootstrap and gitSockets
777 : */
778 : void shutdownConnections();
779 :
780 : /**
781 : * If we change from one network to one another, we will need to update the state of the connections
782 : */
783 : void connectivityChanged();
784 :
785 : /**
786 : * @return getAllNodes() Nodes that are linked to the conversation
787 : */
788 : std::vector<jami::DeviceId> getDeviceIdList() const;
789 :
790 : /**
791 : * Get Typers object
792 : * @return Typers object
793 : */
794 : std::shared_ptr<Typers> typers() const;
795 :
796 : /**
797 : * Get connectivity information for the conversation
798 : * @return map of connectivity info
799 : */
800 : std::vector<std::map<std::string, std::string>> getConnectivity() const;
801 :
802 : private:
803 : std::shared_ptr<Conversation> shared() { return std::static_pointer_cast<Conversation>(shared_from_this()); }
804 : std::shared_ptr<Conversation const> shared() const
805 : {
806 : return std::static_pointer_cast<Conversation const>(shared_from_this());
807 : }
808 4453 : std::weak_ptr<Conversation> weak() { return std::static_pointer_cast<Conversation>(shared_from_this()); }
809 0 : std::weak_ptr<Conversation const> weak() const
810 : {
811 0 : return std::static_pointer_cast<Conversation const>(shared_from_this());
812 : }
813 :
814 : // Private because of weak()
815 : class Impl;
816 : std::unique_ptr<Impl> pimpl_;
817 : };
818 :
819 : } // namespace jami
|