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