diff options
Diffstat (limited to 'Swift')
-rw-r--r-- | Swift/Controllers/Chat/ChatController.cpp | 6 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatController.h | 1 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatControllerBase.h | 1 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 63 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.h | 3 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp | 110 | ||||
-rw-r--r-- | Swift/Controllers/MainController.cpp | 22 | ||||
-rw-r--r-- | Swift/Controllers/MainController.h | 2 | ||||
-rw-r--r-- | Swift/Controllers/UnitTest/MockChatWindow.h | 24 |
9 files changed, 207 insertions, 25 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index b8bf4c3..e36728a 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -312,6 +312,12 @@ void ChatController::handleUIEvent(std::shared_ptr<UIEvent> event) { } } +void ChatController::handleIncomingOwnMessage(std::shared_ptr<Message> message) { + if (!message->getBody().get_value_or("").empty()) { + postSendMessage(message->getBody().get_value_or(""), message); + handleStanzaAcked(message); + } +} void ChatController::postSendMessage(const std::string& body, std::shared_ptr<Stanza> sentStanza) { std::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index 206ee71..99f8e23 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -40,6 +40,7 @@ namespace Swift { virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state); virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/) SWIFTEN_OVERRIDE; virtual ChatWindow* detachChatWindow() SWIFTEN_OVERRIDE; + virtual void handleIncomingOwnMessage(std::shared_ptr<Message> message) SWIFTEN_OVERRIDE; protected: virtual void cancelReplaces() SWIFTEN_OVERRIDE; diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index b97d7af..4255c19 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -54,6 +54,7 @@ namespace Swift { void activateChatWindow(); bool hasOpenWindow() const; virtual void setAvailableServerFeatures(std::shared_ptr<DiscoInfo> info); + virtual void handleIncomingOwnMessage(std::shared_ptr<Message> /*message*/) {} void handleIncomingMessage(std::shared_ptr<MessageEvent> message); std::string addMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time); void replaceMessage(const ChatWindow::ChatMessage& chatMessage, const std::string& id, const boost::posix_time::ptime& time); diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index e8b85c4..f3bb8d3 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -24,9 +24,12 @@ #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Elements/CarbonsReceived.h> +#include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/MUC/MUCBookmarkManager.h> @@ -333,7 +336,7 @@ void ChatsManager::loadRecents() { return; } - foreach(ChatListWindow::Chat chat, recentChats) { + for (auto chat : recentChats) { chat.statusType = StatusShow::None; chat = updateChatStatusAndAvatarHelper(chat); prependRecent(chat); @@ -865,13 +868,49 @@ void ChatsManager::handleUserNicknameChanged(MUCController* mucController, const } } -void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> message) { - JID jid = message->getFrom(); +bool ChatsManager::messageCausesSessionBinding(std::shared_ptr<Message> message) { + bool causesRebind = false; + ChatState::ref chatState = message->getPayload<ChatState>(); + if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { + causesRebind = true; + } + return causesRebind; +} + +void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> incomingMessage) { + std::shared_ptr<Message> message = incomingMessage; + if (message->getFrom().toBare() == jid_.toBare()) { + CarbonsReceived::ref carbonsReceived; + CarbonsSent::ref carbonsSent; + Forwarded::ref forwarded; + Message::ref forwardedMessage; + if ((carbonsReceived = incomingMessage->getPayload<CarbonsReceived>()) && + (forwarded = carbonsReceived->getForwarded()) && + (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) { + message = forwardedMessage; + } + else if ((carbonsSent = incomingMessage->getPayload<CarbonsSent>()) && + (forwarded = carbonsSent->getForwarded()) && + (forwardedMessage = std::dynamic_pointer_cast<Message>(forwarded->getStanza()))) { + JID toJID = forwardedMessage->getTo(); + + ChatController* controller = getChatControllerOrCreate(toJID); + if (controller) { + controller->handleIncomingOwnMessage(forwardedMessage); + } + else { + SWIFT_LOG(error) << "Carbons message ignored." << std::endl; + } + return; + } + } + JID fromJID = message->getFrom(); + std::shared_ptr<MessageEvent> event(new MessageEvent(message)); bool isInvite = !!message->getPayload<MUCInvitationPayload>(); bool isMediatedInvite = (message->getPayload<MUCUserPayload>() && message->getPayload<MUCUserPayload>()->getInvite()); if (isMediatedInvite) { - jid = (*message->getPayload<MUCUserPayload>()->getInvite()).from; + fromJID = (*message->getPayload<MUCUserPayload>()->getInvite()).from; } if (!event->isReadable() && !message->getPayload<ChatState>() && !message->getPayload<DeliveryReceipt>() && !message->getPayload<DeliveryReceiptRequest>() && !isInvite && !isMediatedInvite && !message->hasSubject()) { return; @@ -879,7 +918,7 @@ void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> message) { // Try to deliver it to a MUC if (message->getType() == Message::Groupchat || message->getType() == Message::Error /*|| (isInvite && message->getType() == Message::Normal)*/) { - std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare()); + std::map<JID, MUCController*>::iterator i = mucControllers_.find(fromJID.toBare()); if (i != mucControllers_.end()) { i->second->handleIncomingMessage(event); return; @@ -895,10 +934,10 @@ void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> message) { if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) { if (invite->getIsContinuation()) { // check for existing chat controller for the from JID - ChatController* controller = getChatControllerIfExists(jid); + ChatController* controller = getChatControllerIfExists(fromJID); if (controller) { ChatWindow* window = controller->detachChatWindow(); - chatControllers_.erase(jid); + chatControllers_.erase(fromJID); delete controller; handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window); return; @@ -914,18 +953,12 @@ void ChatsManager::handleIncomingMessage(std::shared_ptr<Message> message) { /* Only route such messages if a window exists, don't open new windows for them.*/ // Do not bind a controller to a full JID, for delivery receipts or chat state notifications. - bool bindControllerToJID = false; - ChatState::ref chatState = message->getPayload<ChatState>(); - if (!message->getBody().get_value_or("").empty() || (chatState && chatState->getChatState() == ChatState::Composing)) { - bindControllerToJID = true; - } - - ChatController* controller = getChatControllerIfExists(jid, bindControllerToJID); + ChatController* controller = getChatControllerIfExists(fromJID, messageCausesSessionBinding(message)); if (controller) { controller->handleIncomingMessage(event); } } else { - getChatControllerOrCreate(jid)->handleIncomingMessage(event); + getChatControllerOrCreate(fromJID)->handleIncomingMessage(event); } } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index da85949..593624d 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -67,7 +67,7 @@ namespace Swift { void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); void setServerDiscoInfo(std::shared_ptr<DiscoInfo> info); - void handleIncomingMessage(std::shared_ptr<Message> message); + void handleIncomingMessage(std::shared_ptr<Message> incomingMessage); std::vector<ChatListWindow::Chat> getRecentChats() const; virtual std::vector<Contact::ref> getContacts(bool withMUCNicks); @@ -105,6 +105,7 @@ namespace Swift { void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf); void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state); boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat); + bool messageCausesSessionBinding(std::shared_ptr<Message> message); void cleanupPrivateMessageRecents(); void appendRecent(const ChatListWindow::Chat& chat); void prependRecent(const ChatListWindow::Chat& chat); diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index f3908d6..e45bcae 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -23,8 +23,11 @@ #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> #include <Swiften/Disco/DummyEntityCapsProvider.h> +#include <Swiften/Elements/CarbonsReceived.h> +#include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> +#include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> @@ -80,11 +83,18 @@ class ChatsManagerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testChatControllerFullJIDBindingOnTypingAndNotActive); CPPUNIT_TEST(testChatControllerPMPresenceHandling); CPPUNIT_TEST(testLocalMUCServiceDiscoveryResetOnDisconnect); + CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite); + + // Highlighting tests CPPUNIT_TEST(testChatControllerHighlightingNotificationTesting); CPPUNIT_TEST(testChatControllerHighlightingNotificationDeduplicateSounds); CPPUNIT_TEST(testChatControllerMeMessageHandling); CPPUNIT_TEST(testChatControllerMeMessageHandlingInMUC); - CPPUNIT_TEST(testPresenceChangeDoesNotReplaceMUCInvite); + + // Carbons tests + CPPUNIT_TEST(testCarbonsForwardedIncomingMessageToSecondResource); + CPPUNIT_TEST(testCarbonsForwardedOutgoingMessageFromSecondResource); + CPPUNIT_TEST_SUITE_END(); public: @@ -921,6 +931,104 @@ public: CPPUNIT_ASSERT_EQUAL(std::string("testling@test.com has gone offline."), MockChatWindow::bodyFromMessage(window->lastAddedPresence_)); } + template <typename CarbonsType> + Message::ref createCarbonsMessage(std::shared_ptr<CarbonsType> carbons, std::shared_ptr<Message> forwardedMessage) { + auto messageWrapper = std::make_shared<Message>(); + messageWrapper->setFrom(jid_.toBare()); + messageWrapper->setTo(jid_); + messageWrapper->setType(Message::Chat); + + messageWrapper->addPayload(carbons); + auto forwarded = std::make_shared<Forwarded>(); + carbons->setForwarded(forwarded); + forwarded->setStanza(forwardedMessage); + return messageWrapper; + } + + void testCarbonsForwardedIncomingMessageToSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + originalMessage->setType(Message::Chat); + std::string forwardedBody = "Some further text."; + originalMessage->setBody(forwardedBody); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(false, window->lastAddedMessageSenderIsSelf_); + } + } + + void testCarbonsForwardedOutgoingMessageFromSecondResource() { + JID messageJID("testling@test.com/resource1"); + JID jid2 = jid_.toBare().withResource("someOtherResource"); + + MockChatWindow* window = new MockChatWindow(); + mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID, uiEventStream_).Return(window); + + std::shared_ptr<Message> message(new Message()); + message->setFrom(messageJID); + std::string body("This is a legible message. >HEH@)oeueu"); + message->setBody(body); + manager_->handleIncomingMessage(message); + CPPUNIT_ASSERT_EQUAL(body, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + + // incoming carbons message from another resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(jid2); + originalMessage->setTo(messageJID); + originalMessage->setType(Message::Chat); + originalMessage->setID("abcdefg123456"); + std::string forwardedBody = "Some text my other resource sent."; + originalMessage->setBody(forwardedBody); + originalMessage->addPayload(std::make_shared<DeliveryReceiptRequest>()); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsSent>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(forwardedBody, MockChatWindow::bodyFromMessage(window->lastAddedMessage_)); + CPPUNIT_ASSERT_EQUAL(true, window->lastAddedMessageSenderIsSelf_); + CPPUNIT_ASSERT_EQUAL(size_t(1), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptRequested, window->receiptChanges_[0].second); + } + + // incoming carbons message for the received delivery receipt to the other resource + { + auto originalMessage = std::make_shared<Message>(); + originalMessage->setFrom(messageJID); + originalMessage->setTo(jid2); + originalMessage->setType(Message::Chat); + originalMessage->addPayload(std::make_shared<DeliveryReceipt>("abcdefg123456")); + + auto messageWrapper = createCarbonsMessage(std::make_shared<CarbonsReceived>(), originalMessage); + + manager_->handleIncomingMessage(messageWrapper); + + CPPUNIT_ASSERT_EQUAL(size_t(2), window->receiptChanges_.size()); + CPPUNIT_ASSERT_EQUAL(ChatWindow::ReceiptReceived, window->receiptChanges_[1].second); + } + } + private: std::shared_ptr<Message> makeDeliveryReceiptTestMessage(const JID& from, const std::string& id) { std::shared_ptr<Message> message = std::make_shared<Message>(); diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index eebac37..a9d3f5c 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -7,13 +7,13 @@ #include <Swift/Controllers/MainController.h> #include <cstdlib> +#include <memory> #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> -#include <memory> -#include <memory> #include <Swiften/Base/Algorithm.h> +#include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> #include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> @@ -35,6 +35,7 @@ #include <Swiften/Network/NetworkFactories.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Queries/Requests/EnableCarbonsRequest.h> #include <Swiften/StringCodecs/Base64.h> #include <Swiften/StringCodecs/Hexify.h> #include <Swiften/VCards/GetVCardRequest.h> @@ -802,9 +803,26 @@ void MainController::handleServerDiscoInfoResponse(std::shared_ptr<DiscoInfo> in rosterController_->getWindow()->setBlockingCommandAvailable(true); rosterController_->initBlockingCommand(); } + if (info->hasFeature(DiscoInfo::MessageCarbonsFeature)) { + enableMessageCarbons(); + } } } +void MainController::enableMessageCarbons() { + auto enableCarbonsRequest = EnableCarbonsRequest::create(client_->getIQRouter()); + enableCarbonsRequestHandlerConnection_ = enableCarbonsRequest->onResponse.connect([&](Payload::ref /*payload*/, ErrorPayload::ref error) { + if (error) { + SWIFT_LOG(warning) << "Failed to enable carbons." << std::endl; + } + else { + SWIFT_LOG(debug) << "Successfully enabled carbons." << std::endl; + } + enableCarbonsRequestHandlerConnection_.disconnect(); + }); + enableCarbonsRequest->send(); +} + void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) { if (!jid.equals(jid_, JID::WithoutResource) || !vCard) { return; diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index 5000d51..4f691ee 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -122,6 +122,7 @@ namespace Swift { void setReconnectTimer(); void resetPendingReconnects(); void resetCurrentError(); + void enableMessageCarbons(); void performLoginFromCachedCredentials(); void reconnectAfterError(); @@ -195,5 +196,6 @@ namespace Swift { HighlightManager* highlightManager_; HighlightEditorController* highlightEditorController_; std::map<std::string, std::string> emoticons_; + boost::signals2::connection enableCarbonsRequestHandlerConnection_; }; } diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 4f874e1..d7942ff 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -7,8 +7,7 @@ #pragma once #include <memory> - -#include <Swiften/Base/foreach.h> +#include <string> #include <Swift/Controllers/UIInterfaces/ChatWindow.h> @@ -18,13 +17,17 @@ namespace Swift { MockChatWindow() : labelsEnabled_(false), impromptuMUCSupported_(false) {} virtual ~MockChatWindow(); - virtual std::string addMessage(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) { + virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) { lastAddedMessage_ = message; + lastAddedMessageSenderName_ = senderName; + lastAddedMessageSenderIsSelf_ = senderIsSelf; return "id"; } - virtual std::string addAction(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) { + virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, std::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/) { lastAddedAction_ = message; + lastAddedActionSenderName_ = senderName; + lastAddedActionSenderIsSelf_ = senderIsSelf; return "id"; } @@ -52,7 +55,9 @@ namespace Swift { virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { } virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { } - virtual void setMessageReceiptState(const std::string &/* id */, ReceiptState /* state */) { } + virtual void setMessageReceiptState(const std::string & id, ReceiptState state) { + receiptChanges_.emplace_back(id, state); + } virtual void setContactChatState(ChatState::ChatStateType /*state*/) {} virtual void setName(const std::string& name) {name_ = name;} @@ -101,7 +106,7 @@ namespace Swift { std::string body; std::shared_ptr<ChatTextMessagePart> text; std::shared_ptr<ChatHighlightingMessagePart> highlight; - foreach (std::shared_ptr<ChatMessagePart> part, message.getParts()) { + for (auto &&part : message.getParts()) { if ((text = std::dynamic_pointer_cast<ChatTextMessagePart>(part))) { body += text->text; } @@ -114,11 +119,17 @@ namespace Swift { void resetLastMessages() { lastAddedMessage_ = lastAddedAction_ = lastAddedPresence_ = lastReplacedMessage_ = lastAddedSystemMessage_ = lastReplacedSystemMessage_ = ChatMessage(); + lastAddedMessageSenderName_ = lastAddedActionSenderName_ = ""; + lastAddedMessageSenderIsSelf_ = lastAddedActionSenderIsSelf_ = false; } std::string name_; ChatMessage lastAddedMessage_; + std::string lastAddedMessageSenderName_; + bool lastAddedMessageSenderIsSelf_; ChatMessage lastAddedAction_; + std::string lastAddedActionSenderName_; + bool lastAddedActionSenderIsSelf_; ChatMessage lastAddedPresence_; ChatMessage lastReplacedMessage_; ChatMessage lastAddedSystemMessage_; @@ -129,6 +140,7 @@ namespace Swift { bool impromptuMUCSupported_; SecurityLabelsCatalog::Item label_; Roster* roster_; + std::vector<std::pair<std::string, ReceiptState>> receiptChanges_; }; } |