diff --git a/bindings/Objective-C/MEGAChatSdk.h b/bindings/Objective-C/MEGAChatSdk.h index 3cd880c6c..8c33aae0a 100644 --- a/bindings/Objective-C/MEGAChatSdk.h +++ b/bindings/Objective-C/MEGAChatSdk.h @@ -171,10 +171,10 @@ typedef NS_ENUM (NSInteger, MEGAChatConnection) { - (void)userEmailByUserHandle:(uint64_t)userHandle delegate:(id)delegate; - (void)userEmailByUserHandle:(uint64_t)userHandle; -- (void)userFirstnameByUserHandle:(uint64_t)userHandle delegate:(id)delegate; -- (void)userFirstnameByUserHandle:(uint64_t)userHandle; -- (void)userLastnameByUserHandle:(uint64_t)userHandle delegate:(id)delegate; -- (void)userLastnameByUserHandle:(uint64_t)userHandle; +- (void)userFirstnameByUserHandle:(uint64_t)userHandle authorizationToken:(NSString *)authorizationToken delegate:(id)delegate; +- (void)userFirstnameByUserHandle:(uint64_t)userHandle authorizationToken:(NSString *)authorizationToken; +- (void)userLastnameByUserHandle:(uint64_t)userHandle authorizationToken:(NSString *)authorizationToken delegate:(id)delegate; +- (void)userLastnameByUserHandle:(uint64_t)userHandle authorizationToken:(NSString *)authorizationToken; - (NSString *)contacEmailByHandle:(uint64_t)userHandle; - (uint64_t)userHandleByEmail:(NSString *)email; diff --git a/bindings/java/nz/mega/sdk/MegaChatApiJava.java b/bindings/java/nz/mega/sdk/MegaChatApiJava.java index 0b372ab0c..da5326b9a 100644 --- a/bindings/java/nz/mega/sdk/MegaChatApiJava.java +++ b/bindings/java/nz/mega/sdk/MegaChatApiJava.java @@ -7,8 +7,11 @@ import java.util.Set; import mega.privacy.android.app.MegaApplication; +import mega.privacy.android.app.R; import mega.privacy.android.app.utils.VideoCaptureUtils; +import static nz.mega.sdk.MegaChatError.*; + public class MegaChatApiJava { MegaChatApi megaChatApi; static DelegateMegaChatLogger logger; @@ -3074,4 +3077,35 @@ static ArrayList chatRoomListItemToArray(MegaChatListItemList return result; } + + /** + * Gets the translated string of an error received in a request. + * + * @param error MegaChatError received in the request + * @return The translated string + */ + public static String getTranslatedErrorString(MegaChatError error) { + MegaApplication app = MegaApplication.getInstance(); + if (app == null) { + return error.getErrorString(); + } + + switch (error.getErrorCode()) { + case ERROR_OK: + return app.getString(R.string.error_ok); + case ERROR_ARGS: + return app.getString(R.string.error_args); + case ERROR_ACCESS: + return app.getString(R.string.error_access); + case ERROR_NOENT: + return app.getString(R.string.error_noent); + case ERROR_EXIST: + return app.getString(R.string.error_exist); + case ERROR_TOOMANY: + return app.getString(R.string.error_toomany); + case ERROR_UNKNOWN: + default: + return app.getString(R.string.error_unknown); + } + } }; diff --git a/examples/megaclc/megaclc.cpp b/examples/megaclc/megaclc.cpp index 4af54aa9a..9e79f54f0 100644 --- a/examples/megaclc/megaclc.cpp +++ b/examples/megaclc/megaclc.cpp @@ -90,6 +90,7 @@ std::atomic g_reportMessagesDeveloper{false}; // These objects are helping to work around history loading problems for reviewing public chats std::atomic g_reviewingPublicChat{false}; std::atomic g_reviewPublicChatMsgCountRemaining{0}; +std::atomic g_reviewPublicChatid{c::MEGACHAT_INVALID_HANDLE}; std::unique_ptr g_reviewPublicChatOutFile; std::unique_ptr g_reviewPublicChatOutFileLinks; std::mutex g_reviewPublicChatOutFileLogsMutex; @@ -97,6 +98,7 @@ std::unique_ptr g_reviewPublicChatOutFileLogs; std::map g_reviewPublicChatEmails; std::map g_reviewPublicChatFirstnames; std::map g_reviewPublicChatLastnames; +class ReviewPublicChat_GetUserEmail_Listener; #ifdef __APPLE__ // No std::fileystem before OSX10.15 @@ -518,6 +520,114 @@ static void store_line(char* l) } #endif +void reviewPublicChatLoadMessages(const c::MegaChatHandle chatid); + +struct CLCRoomListener : public c::MegaChatRoomListener +{ + c::MegaChatHandle room = c::MEGACHAT_INVALID_HANDLE; + + void onChatRoomUpdate(c::MegaChatApi*, c::MegaChatRoom *chat) override; + + void onMessageLoaded(c::MegaChatApi*, c::MegaChatMessage *msg) override; + + void onMessageReceived(c::MegaChatApi*, c::MegaChatMessage *) override; + + void onMessageUpdate(c::MegaChatApi*, c::MegaChatMessage *msg) override; + + void onHistoryReloaded(c::MegaChatApi*, c::MegaChatRoom *chat) override; +}; + +struct RoomListenerRecord +{ + bool open = false; + unique_ptr listener; + RoomListenerRecord(); +}; +map g_roomListeners; + +class ReviewPublicChat_GetUserEmail_Listener : public c::MegaChatRequestListener +{ +public: + ReviewPublicChat_GetUserEmail_Listener() = default; + + ReviewPublicChat_GetUserEmail_Listener(const ReviewPublicChat_GetUserEmail_Listener&) = delete; + ReviewPublicChat_GetUserEmail_Listener& operator=(const ReviewPublicChat_GetUserEmail_Listener&) = delete; + + void onRequestFinish(c::MegaChatApi* api, c::MegaChatRequest *request, c::MegaChatError* e) override + { + // Called on MegaChat API thread + if (request->getType() != c::MegaChatRequest::TYPE_GET_EMAIL) + { + return; + } + if (!check_err("getUserEmail", e)) + { + *g_reviewPublicChatOutFile << "getUserEmail failed. Error: " << std::string{e->getErrorString()} << endl; + return; + } + const char *email = request->getText(); + if (!email) + { + *g_reviewPublicChatOutFile << "getUserEmail failed. Error: Email empty" << endl; + return; + } + g_apiLogger.logMsg(m::MegaApi::LOG_LEVEL_INFO, "ReviewPublicChat: getUserEmail finished"); + g_apiLogger.logMsg(m::MegaApi::LOG_LEVEL_INFO, "ReviewPublicChat: Email: " + std::string{email}); + g_reviewPublicChatEmails[request->getUserHandle()] = email; + std::ostringstream os; + os << "\tEmail: " + std::string{email} + << " (" << g_reviewPublicChatEmails.size() << " / " << m_userCount.load() << ")" << endl; + const auto msg = os.str(); + conlock(cout) << msg; + conlock(*g_reviewPublicChatOutFile) << msg << flush; + if (g_reviewPublicChatEmails.size() < static_cast(m_userCount.load())) + { + // Wait until we've received emails for all users + return; + } + + api->removeChatRequestListener(this); + + // Access to g_roomListeners is safe because no other thread accesses this map + // while the Mega Chat API thread is using it here. + const auto chatid = m_chatId.load(); + auto& rec = g_roomListeners[chatid]; + assert(!rec.open); + if (!api->openChatRoom(chatid, rec.listener.get())) + { + g_chatLogger.logMsg(c::MegaChatApi::LOG_LEVEL_ERROR, "Failed to open chat room"); + g_roomListeners.erase(chatid); + *g_reviewPublicChatOutFile << "Error: Failed to open chat room." << endl; + } + else + { + rec.listener->room = chatid; + rec.open = true; + } + + if (api->getChatConnectionState(chatid) == c::MegaChatApi::CHAT_CONNECTION_ONLINE) + { + g_reportMessagesDeveloper = false; + reviewPublicChatLoadMessages(chatid); + } + // else --> onChatConnectionState() will call reviewPublicChatLoadMessages() when online + } + + void setUserCount(int count) + { + m_userCount = count; + } + + void setChatId(c::MegaChatHandle chatid) + { + m_chatId = chatid; + } + +private: + std::atomic m_userCount{0}; + std::atomic m_chatId{0}; +}; + struct CLCListener : public c::MegaChatListener { void onChatInitStateUpdate(c::MegaChatApi*, int newState) override @@ -557,6 +667,46 @@ struct CLCListener : public c::MegaChatListener } } } + + void onChatConnectionStateUpdate(c::MegaChatApi* api, c::MegaChatHandle chatid, int newState) override + { + if (newState != c::MegaChatApi::CHAT_CONNECTION_ONLINE + || !g_reviewingPublicChat + || chatid != g_reviewPublicChatid + || g_reviewPublicChatMsgCountRemaining <= 0) + { + return; + } + + // wait until all emails are loaded before start loading messages + std::unique_ptr chatRoom(api->getChatRoom(chatid)); + unsigned int numParticipants = chatRoom->getPeerCount(); + if (g_reviewPublicChatEmails.empty() && numParticipants != 0) + { + + static ReviewPublicChat_GetUserEmail_Listener get_user_email_listener; + // Note: We need to be logged in to receive user emails + + get_user_email_listener.setUserCount(numParticipants); + get_user_email_listener.setChatId(g_reviewPublicChatid); + api->addChatRequestListener(&get_user_email_listener); + + // load emails for all participants + for (unsigned int i = 0; i < numParticipants; i++) + { + api->getUserEmail(chatRoom->getPeerHandle(i)); + } + return; + } + + bool allEmailsReceived = g_reviewPublicChatEmails.size() >= numParticipants; + if (!allEmailsReceived) + { + return; + } + + reviewPublicChatLoadMessages(chatid); + } }; struct finishInfo @@ -874,37 +1024,7 @@ void MegaclcListener::onRequestFinish(m::MegaApi* api, m::MegaRequest *request, } bool oneOpenRoom(c::MegaChatHandle room); - -void reviewPublicChatLoadMessages(const c::MegaChatHandle chatid) -{ - constexpr int errorRetryCount = 10; - int source = c::MegaChatApi::SOURCE_NONE; - for (int i = 0; i < errorRetryCount; ++i) - { - source = g_chatApi->loadMessages(chatid, g_reviewPublicChatMsgCountRemaining.load()); - - auto cl = conlock(cout); - switch (source) - { - case c::MegaChatApi::SOURCE_ERROR: - { - cout << "Load failed as we are offline." << endl; - continue; - } - case c::MegaChatApi::SOURCE_NONE: - { - g_reviewPublicChatMsgCountRemaining = 0; - cout << "No more messages." << endl; - return; - } - default: return; - } - } - if (source == c::MegaChatApi::SOURCE_ERROR) - { - g_reviewPublicChatMsgCountRemaining = 0; - } -} +void reviewPublicChatLoadMessages(const c::MegaChatHandle chatid); void reviewPublicChatFetchFirstName(const c::MegaChatRoom& room, const c::MegaChatHandle userHandle) { @@ -1018,12 +1138,8 @@ void reportMessageHuman(c::MegaChatHandle chatid, c::MegaChatMessage *msg, const { if (!msg) { - if (g_chatApi->isFullHistoryLoaded(chatid)) - { - return; - } cout << "Room " << ch_s(chatid) << " - end of " << loadorreceive << " messages" << endl; - if (g_reviewingPublicChat && g_reviewPublicChatMsgCountRemaining > 0) + if (g_reviewingPublicChat) { reviewPublicChatLoadMessages(chatid); } @@ -1190,11 +1306,11 @@ void reportMessageHuman(c::MegaChatHandle chatid, c::MegaChatMessage *msg, const } } -void reportMessage(c::MegaChatHandle room, c::MegaChatMessage *msg, const char* loadorreceive) +void reportMessage(c::MegaChatHandle chatid, c::MegaChatMessage *msg, const char* loadorreceive) { if (!g_reportMessagesDeveloper) { - reportMessageHuman(room, msg, loadorreceive); + reportMessageHuman(chatid, msg, loadorreceive); return; } @@ -1202,22 +1318,22 @@ void reportMessage(c::MegaChatHandle room, c::MegaChatMessage *msg, const char* if (!msg) { - cout << "Room " << ch_s(room) << " - end of " << loadorreceive << " messages" << endl; + cout << "Room " << ch_s(chatid) << " - end of " << loadorreceive << " messages" << endl; return; } if (!g_detailHigh && msg->getType() == c::MegaChatMessage::TYPE_NORMAL && msg->getContent()) { cout << ch_s(msg->getUserHandle()); - if (!oneOpenRoom(room)) + if (!oneOpenRoom(chatid)) { - cout << " (room " << ch_s(room) << ")"; + cout << " (room " << ch_s(chatid) << ")"; } cout << ": " << msg->getContent() << endl; return; } - cout << "Room " << ch_s(room) << " " << loadorreceive << " message " << msg->getMsgIndex() << " from " << ch_s(msg->getUserHandle()) << " type: "; + cout << "Room " << ch_s(chatid) << " " << loadorreceive << " message " << msg->getMsgIndex() << " from " << ch_s(msg->getUserHandle()) << " type: "; cout << msgTypeToString(msg->getType()); @@ -1275,62 +1391,53 @@ void reportMessage(c::MegaChatHandle room, c::MegaChatMessage *msg, const char* { cout << " (attached users: " << msg->getUsersCount() << ")"; } - - //MegaChatHandle getUserHandle(unsigned int index) const; - //const char *getUserName(unsigned int index) const; - //const char *getUserEmail(unsigned int index) const; - //mega::MegaNodeList *getMegaNodeList() const; cout << endl; - } - -struct CLCRoomListener : public c::MegaChatRoomListener +bool oneOpenRoom(c::MegaChatHandle room) { - c::MegaChatHandle room = c::MEGACHAT_INVALID_HANDLE; - - void onChatRoomUpdate(c::MegaChatApi*, c::MegaChatRoom *chat) override - { - g_chatLogger.logMsg(c::MegaChatApi::LOG_LEVEL_INFO, "Room " + ch_s(chat->getChatId()) + " updated"); - } - - void onMessageLoaded(c::MegaChatApi*, c::MegaChatMessage *msg) override - { - reportMessage(room, msg, "loaded"); - } + return g_roomListeners.size() == 1 && g_roomListeners.begin()->first == room; +} - void onMessageReceived(c::MegaChatApi*, c::MegaChatMessage *msg) override +void reviewPublicChatLoadMessages(const c::MegaChatHandle chatid) +{ + int source; + if (g_chatApi->isFullHistoryLoaded(chatid)) { - reportMessage(room, msg, "received"); + source = c::MegaChatApi::SOURCE_NONE; } - - void onMessageUpdate(c::MegaChatApi*, c::MegaChatMessage *msg) override + else { - reportMessage(room, msg, "updated"); + source = g_chatApi->loadMessages(chatid, g_reviewPublicChatMsgCountRemaining.load()); } - void onHistoryReloaded(c::MegaChatApi*, c::MegaChatRoom *chat) override + auto cl = conlock(cout); + switch (source) { - conlock(cout) << "Room " << room << " notification that room " << chat->getChatId() << " is reloading" << endl; + case c::MegaChatApi::SOURCE_ERROR: + { + cout << "Loading messages..." << endl; + break; + } + case c::MegaChatApi::SOURCE_NONE: + { + cout << "No more messages." << endl; + + g_reviewingPublicChat = false; + g_reviewPublicChatEmails.clear(); + g_reviewPublicChatFirstnames.clear(); + g_reviewPublicChatLastnames.clear(); + g_reviewPublicChatMsgCountRemaining = 0; + g_reviewPublicChatid = c::MEGACHAT_INVALID_HANDLE; + g_chatApi->closeChatRoom(chatid, g_roomListeners[chatid].listener.get()); + g_roomListeners.erase(chatid); + g_chatApi->closeChatPreview(chatid); + return; + } + default: return; } -}; - - -struct RoomListenerRecord -{ - bool open = false; - unique_ptr listener; - RoomListenerRecord() : listener(new CLCRoomListener) {} -}; - -map g_roomListeners; - -bool oneOpenRoom(c::MegaChatHandle room) -{ - return g_roomListeners.size() == 1 && g_roomListeners.begin()->first == room; } - bool extractflag(const string& flag, vector& words) { for (auto i = words.begin(); i != words.end(); ++i) @@ -2045,102 +2152,6 @@ void exec_loadmessages(ac::ACState& s) } } -class ReviewPublicChat_GetUserEmail_Listener : public m::MegaListener -{ -public: - ReviewPublicChat_GetUserEmail_Listener() = default; - - ReviewPublicChat_GetUserEmail_Listener(const ReviewPublicChat_GetUserEmail_Listener&) = delete; - ReviewPublicChat_GetUserEmail_Listener& operator=(const ReviewPublicChat_GetUserEmail_Listener&) = delete; - - void onRequestFinish(m::MegaApi* api, m::MegaRequest *request, m::MegaError* e) override - { - // Called on Mega API thread - if (request->getType() != m::MegaRequest::TYPE_GET_USER_EMAIL) - { - return; - } - if (!check_err("getUserEmail", e)) - { - *g_reviewPublicChatOutFile << "getUserEmail failed. Error: " << std::string{e->getErrorString()} << endl; - return; - } - if (!request->getEmail()) - { - *g_reviewPublicChatOutFile << "getUserEmail failed. Error: Email empty" << endl; - return; - } - g_apiLogger.logMsg(m::MegaApi::LOG_LEVEL_INFO, "ReviewPublicChat: getUserEmail finished"); - g_apiLogger.logMsg(m::MegaApi::LOG_LEVEL_INFO, "ReviewPublicChat: Email: " + std::string{request->getEmail()}); - g_reviewPublicChatEmails[request->getNodeHandle()] = request->getEmail(); - std::ostringstream os; - os << "ReviewPublicChat: Email: " + std::string{request->getEmail()} - << " (" << g_reviewPublicChatEmails.size() << " / " << m_userCount.load() << ")" << endl; - const auto msg = os.str(); - conlock(cout) << msg; - conlock(*g_reviewPublicChatOutFile) << msg << flush; - if (g_reviewPublicChatEmails.size() < static_cast(m_userCount.load())) - { - // Wait until we've received emails for all users - return; - } - - api->removeListener(this); - - const auto chatid = m_chatId.load(); - - // Access to g_roomListeners is safe because no other thread accesses this map - // while the Mega Chat API thread is using it here. - auto& rec = g_roomListeners[chatid]; - if (!rec.open) - { - if (!g_chatApi->openChatRoom(chatid, rec.listener.get())) - { - g_chatLogger.logMsg(c::MegaChatApi::LOG_LEVEL_ERROR, - "Failed to open chat room"); - g_roomListeners.erase(chatid); - *g_reviewPublicChatOutFile << "Error: Failed to open chat room." << endl; - } - else - { - rec.listener->room = chatid; - rec.open = true; - } - } - - if (rec.open) - { - g_reportMessagesDeveloper = false; - reviewPublicChatLoadMessages(chatid); - } - } - - void setUserCount(int count) - { - m_userCount = count; - } - - void setChatId(c::MegaChatHandle chatid) - { - m_chatId = chatid; - } - -private: - std::atomic m_userCount{0}; - std::atomic m_chatId{0}; -}; - -void closeAllRooms() -{ - for (const auto& pair : g_roomListeners) - { - const auto chatId = pair.first; - g_chatApi->closeChatRoom(chatId, pair.second.listener.get()); - g_chatApi->closeChatPreview(chatId); - } - g_roomListeners.clear(); -} - bool initFile(std::unique_ptr& file, const std::string& filename) { #ifdef __APPLE__ @@ -2165,13 +2176,12 @@ void exec_reviewpublicchat(ac::ACState& s) return; } - closeAllRooms(); - g_reviewingPublicChat = true; g_reviewPublicChatEmails.clear(); g_reviewPublicChatFirstnames.clear(); g_reviewPublicChatLastnames.clear(); g_reviewPublicChatMsgCountRemaining = 0; + g_reviewPublicChatid = c::MEGACHAT_INVALID_HANDLE; const auto chat_link = s.words[1].s; g_reviewPublicChatMsgCountRemaining = s.words.size() > 2 ? stoi(s.words[2].s) : 5000; @@ -2202,90 +2212,56 @@ void exec_reviewpublicchat(ac::ACState& s) *g_reviewPublicChatOutFileLinks << chat_link << endl; *g_reviewPublicChatOutFileLogs << chat_link << endl; - static ReviewPublicChat_GetUserEmail_Listener get_user_email_listener; - // Note: We need to be logged in to receive user emails - - auto connect_listener = new OneShotChatRequestListener; - auto open_chat_preview_listener = new OneShotChatRequestListener; + auto check_chat_preview_listener = new OneShotChatRequestListener; + check_chat_preview_listener->onRequestFinishFunc = + [](c::MegaChatApi* api, c::MegaChatRequest *request, c::MegaChatError* e) + { + // Called on Mega Chat API thread + if (!check_err("checkChatLink", e)) + { + *g_reviewPublicChatOutFile << "checkChatLink failed. Error: " << e->getErrorString() << endl; + return; + } - connect_listener->onRequestFinishFunc = - [chat_link, open_chat_preview_listener](c::MegaChatApi* api, c::MegaChatRequest *request, c::MegaChatError* e) + const c::MegaChatHandle chatid = g_reviewPublicChatid = request->getChatHandle(); + std::ostringstream os1; + os1 << "\nReviewPublicChat: chatlink loaded succesfully.\n\tChatid: " << k::Id(g_reviewPublicChatid).toString() << endl; + const auto msg1 = os1.str(); + conlock(cout) << msg1; + conlock(*g_reviewPublicChatOutFile) << msg1 << flush; + + const int numPeers = static_cast(request->getNumber()); + std::ostringstream os2; + os2 << "\tUser count: " << numPeers << endl; + const auto msg2 = os2.str(); + conlock(cout) << msg2; + conlock(*g_reviewPublicChatOutFile) << msg2 << flush; + + const char *title = request->getText(); + std::ostringstream os3; + os3 << "\tTitle: " << title << endl; + const auto msg3 = os3.str(); + conlock(cout) << msg3; + conlock(*g_reviewPublicChatOutFile) << msg3 << flush; + + // now we know the chatid, we register the listener + auto open_chat_preview_listener = new OneShotChatRequestListener; + open_chat_preview_listener->onRequestFinishFunc = + [chatid](c::MegaChatApi*, c::MegaChatRequest *request, c::MegaChatError* e) + { + if (!check_err("openChatPreview", e)) { - // Called on Mega Chat API thread - if (request->getType() != c::MegaChatRequest::TYPE_CONNECT) - { - return; - } - if (!check_err("connect", e)) - { - *g_reviewPublicChatOutFile << "connect failed. Error: " << e->getErrorString() << endl; - return; - } - conlock(cout) << "Connection state " << api->getConnectionState() << endl; - g_chatApi->openChatPreview(chat_link.c_str(), open_chat_preview_listener); - }; + *g_reviewPublicChatOutFile << "openChatPreview failed. Error: " << e->getErrorString() << endl; + return; + } + }; - open_chat_preview_listener->onRequestFinishFunc = - [](c::MegaChatApi*, c::MegaChatRequest *request, c::MegaChatError* e) - { - // Called on Mega Chat API thread - if (request->getType() != c::MegaChatRequest::TYPE_LOAD_PREVIEW) - { - return; - } - if (!check_err("openChatPreview", e)) - { - *g_reviewPublicChatOutFile << "openChatPreview failed. Error: " << e->getErrorString() << endl; - return; - } - const auto chatid = request->getChatHandle(); - const int user_count = static_cast(request->getNumber()); - - std::ostringstream os1; - os1 << "openChatPreview: chatlink loaded. Chatid: " << k::Id(chatid).toString() << endl; - const auto msg1 = os1.str(); - conlock(cout) << msg1; - conlock(*g_reviewPublicChatOutFile) << msg1 << flush; - - std::ostringstream os2; - os2 << "openChatPreview: User count: " << user_count << endl; - const auto msg2 = os2.str(); - conlock(cout) << msg2; - conlock(*g_reviewPublicChatOutFile) << msg2 << flush; - - get_user_email_listener.setUserCount(user_count); - get_user_email_listener.setChatId(chatid); - g_megaApi->addListener(&get_user_email_listener); - - auto push_received_listener = new OneShotChatRequestListener; - - push_received_listener->onRequestFinishFunc = - [chatid](c::MegaChatApi*, c::MegaChatRequest *request, c::MegaChatError* e) - { - // Called on Mega Chat API thread - if (request->getType() != c::MegaChatRequest::TYPE_PUSH_RECEIVED) - { - return; - } - if (!check_err("pushReceived", e)) - { - *g_reviewPublicChatOutFile << "pushReceived failed. Error: " << e->getErrorString() << endl; - return; - } - g_chatLogger.logMsg(c::MegaChatApi::LOG_LEVEL_INFO, - "ReviewPublicChat: pushReceived finished"); - - std::unique_ptr chatRoom{g_chatApi->getChatRoom(chatid)}; - for (unsigned int i = 0; i < chatRoom->getPeerCount(); i++) - { - g_megaApi->getUserEmail(chatRoom->getPeerHandle(i)); - } - }; - - g_chatApi->pushReceived(false, push_received_listener); - }; + const char *chatlink = request->getLink(); + api->openChatPreview(chatlink, open_chat_preview_listener); + // now wait until logged in into the chatroom, so we know the peers and load their emails + }; - g_chatApi->connect(connect_listener); + g_chatApi->checkChatLink(chat_link.c_str(), check_chat_preview_listener); } void exec_isfullhistoryloaded(ac::ACState& s) @@ -4162,3 +4138,21 @@ int main() g_chatApi.reset(); g_megaApi.reset(); } + +RoomListenerRecord::RoomListenerRecord() : listener(new CLCRoomListener) {} + +void CLCRoomListener::onChatRoomUpdate(megachat::MegaChatApi *, megachat::MegaChatRoom *chat) +{ + g_chatLogger.logMsg(c::MegaChatApi::LOG_LEVEL_INFO, "Room " + ch_s(chat->getChatId()) + " updated"); +} + +void CLCRoomListener::onMessageLoaded(megachat::MegaChatApi *, megachat::MegaChatMessage *msg) +{ + reportMessage(room, msg, "loaded"); +} + +void CLCRoomListener::onMessageReceived(megachat::MegaChatApi *, megachat::MegaChatMessage *) {} + +void CLCRoomListener::onMessageUpdate(megachat::MegaChatApi *, megachat::MegaChatMessage *msg) {} + +void CLCRoomListener::onHistoryReloaded(megachat::MegaChatApi *, megachat::MegaChatRoom *chat) {} diff --git a/examples/qtmegachatapi/MainWindow.cpp b/examples/qtmegachatapi/MainWindow.cpp index 1b6bd92c4..61acd3f3b 100644 --- a/examples/qtmegachatapi/MainWindow.cpp +++ b/examples/qtmegachatapi/MainWindow.cpp @@ -860,8 +860,8 @@ void MainWindow::onChatListItemUpdate(MegaChatApi *, MegaChatListItem *item) } itemController = addOrUpdateChatControllerItem(item->copy()); - if (!mAllowOrder - && !(item->hasChanged(megachat::MegaChatListItem::CHANGE_TYPE_UPDATE_PREVIEWERS))) + bool needreorder = needReorder(item, oldPriv); + if ((!mAllowOrder && needreorder) || (!needreorder && !widget)) { return; } @@ -884,7 +884,7 @@ void MainWindow::onChatListItemUpdate(MegaChatApi *, MegaChatListItem *item) // If we don't need to reorder and chatItemwidget is rendered // we need to update the widget because non order actions requires // a live update of widget - if (!needReorder(item, oldPriv) && widget) + if (!needreorder) { //Last Message update if (item->hasChanged(megachat::MegaChatListItem::CHANGE_TYPE_LAST_MSG)) @@ -926,7 +926,7 @@ void MainWindow::onChatListItemUpdate(MegaChatApi *, MegaChatListItem *item) } } } - else if(mNeedReorder) + else { reorderAppChatList(); } diff --git a/examples/qtmegachatapi/MegaChatApplication.cpp b/examples/qtmegachatapi/MegaChatApplication.cpp index 57ad1e450..cfcd174a5 100644 --- a/examples/qtmegachatapi/MegaChatApplication.cpp +++ b/examples/qtmegachatapi/MegaChatApplication.cpp @@ -437,7 +437,7 @@ void MegaChatApplication::onRequestFinish(MegaApi *api, MegaRequest *request, Me int error = e->getErrorCode(); if (error != API_OK && (reqType != MegaRequest::TYPE_LOGIN || error != MegaError::API_EMFAREQUIRED) - && (reqType != MegaRequest::TYPE_GET_ATTR_USER || error != MegaError::API_ENOENT)) + && (reqType != MegaRequest::TYPE_GET_ATTR_USER)) { QMessageBox::critical(nullptr, tr("SDK Request failed: ").append(request->getRequestString()), tr("Error: ").append(e->getErrorString())); } diff --git a/examples/qtmegachatapi/SettingWindow.cpp b/examples/qtmegachatapi/SettingWindow.cpp index dac4805f9..fcb028485 100644 --- a/examples/qtmegachatapi/SettingWindow.cpp +++ b/examples/qtmegachatapi/SettingWindow.cpp @@ -16,6 +16,8 @@ SettingWindow::SettingWindow(MegaChatApplication *app) : connect(ui->confirmButtons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(onClicked(QAbstractButton*))); ui->globalDnd->setValidator(new QIntValidator(0, 31536000, this)); // Max value -> seconds in a year connect(ui->globalNotificationsEnabled, SIGNAL(clicked(bool)), this, SLOT(onGlobalClicked(bool))); + ui->globalChatDnd->setValidator(new QIntValidator(0, 31536000, this)); // Max value -> seconds in a year + connect(ui->globalChatNotificationsEnabled, SIGNAL(clicked(bool)), this, SLOT(onGlobalChatClicked(bool))); connect(ui->scheduleEnabled, SIGNAL(clicked(bool)), this, SLOT(onScheduleEnabled(bool))); if (!mApp->getNotificationSettings() || !mApp->getTimeZoneDetails()) { @@ -52,16 +54,22 @@ void SettingWindow::onPushNotificationSettingsUpdate() } ::mega::m_time_t now = ::mega::m_time(NULL); - ui->chats->setChecked(notificationSettings->isChatsEnabled()); ui->pcr->setChecked(notificationSettings->isContactsEnabled()); ui->shares->setChecked(notificationSettings->isSharesEnabled()); - ui->globalNotificationsEnabled->setChecked(notificationSettings->isGlobalEnabled()); + ui->globalNotificationsEnabled->setChecked(!notificationSettings->isGlobalDndEnabled()); mGlobalDifference = notificationSettings->getGlobalDnd() - now; mGlobalDifference = (mGlobalDifference >= 0) ? mGlobalDifference : 0; std::string globalDnd = std::to_string(mGlobalDifference); ui->globalDnd->setText(globalDnd.c_str()); - ui->globalDnd->setEnabled(!notificationSettings->isGlobalEnabled()); + ui->globalDnd->setEnabled(notificationSettings->isGlobalDndEnabled()); + + ui->globalChatNotificationsEnabled->setChecked(!notificationSettings->isGlobalChatsDndEnabled()); + mGlobalChatsDifference = notificationSettings->getGlobalChatsDnd() - now; + mGlobalChatsDifference = (mGlobalChatsDifference >= 0) ? mGlobalChatsDifference : 0; + std::string globalChatsDnd = std::to_string(mGlobalChatsDifference); + ui->globalChatDnd->setText(globalChatsDnd.c_str()); + ui->globalChatDnd->setEnabled(notificationSettings->isGlobalChatsDndEnabled()); ui->scheduleEnabled->setChecked(notificationSettings->isGlobalScheduleEnabled()); onScheduleEnabled(notificationSettings->isGlobalScheduleEnabled()); @@ -146,13 +154,14 @@ void SettingWindow::savePushNotificationSettings() bool updated = false; ::mega::m_time_t now = ::mega::m_time(NULL); int globalDnd = ui->globalDnd->text().toInt(); + int globalChatsDnd = ui->globalChatDnd->text().toInt(); auto notificationSettings = mApp->getNotificationSettings(); assert(notificationSettings); /* If globalNotifications checkbox changed respect initial value or if globalNotifications checkbox is disabled and do not disturb period has been changed */ - if (ui->globalNotificationsEnabled->isChecked() != notificationSettings->isGlobalEnabled() + if (ui->globalNotificationsEnabled->isChecked() == notificationSettings->isGlobalDndEnabled() || (!ui->globalNotificationsEnabled->isChecked() && mGlobalDifference != globalDnd)) { updated = true; @@ -174,11 +183,26 @@ void SettingWindow::savePushNotificationSettings() } } - // Enable/disable notifications related to all chats - if (ui->chats->isChecked() != notificationSettings->isChatsEnabled()) + if (ui->globalChatNotificationsEnabled->isChecked() == notificationSettings->isGlobalChatsDndEnabled() + || (!ui->globalChatNotificationsEnabled->isChecked() && mGlobalChatsDifference != globalChatsDnd)) { - notificationSettings->enableChats(ui->chats->isChecked()); updated = true; + // If we want to enable global chat notifications by setting mGlobalChatsDND to -1 + if (ui->globalChatNotificationsEnabled->isChecked()) + { + notificationSettings->enableChats(true); + } + else + { + if (globalChatsDnd) + { // If we want to set a valid do not disturb period + notificationSettings->setGlobalChatsDnd(globalChatsDnd + now); + } + else + { // If we want to disable global chat notifications by setting mGlobalChatsDND to 0 + notificationSettings->enableChats(false); + } + } } // Enable/disable notifications related to all contacts @@ -278,6 +302,11 @@ void SettingWindow::onGlobalClicked(bool value) ui->globalDnd->setEnabled(!value); } +void SettingWindow::onGlobalChatClicked(bool value) +{ + ui->globalChatDnd->setEnabled(!value); +} + void SettingWindow::onScheduleEnabled(bool value) { ui->startTime->setEnabled(value); diff --git a/examples/qtmegachatapi/SettingWindow.h b/examples/qtmegachatapi/SettingWindow.h index ac66b7c65..6d45a80d0 100644 --- a/examples/qtmegachatapi/SettingWindow.h +++ b/examples/qtmegachatapi/SettingWindow.h @@ -32,6 +32,7 @@ class SettingWindow : public QDialog // notification settings ::mega::m_time_t mGlobalDifference = -1; + ::mega::m_time_t mGlobalChatsDifference = -1; QStandardItemModel mNotificationSettingsPerChat; void savePushNotificationSettings(); @@ -42,6 +43,7 @@ class SettingWindow : public QDialog private slots: void onClicked(QAbstractButton*); void onGlobalClicked(bool value); + void onGlobalChatClicked(bool value); void onScheduleEnabled(bool value); void on_autoAwayCheckBox_clicked(bool checked); }; diff --git a/examples/qtmegachatapi/SettingWindow.ui b/examples/qtmegachatapi/SettingWindow.ui index 8bfb00121..2e8ac0b9d 100644 --- a/examples/qtmegachatapi/SettingWindow.ui +++ b/examples/qtmegachatapi/SettingWindow.ui @@ -32,13 +32,6 @@ General - - - - Chats - - - @@ -52,13 +45,67 @@ Shares + + + + + + + Global chat notifications enabled + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Do not disturb period(s): + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - Global notifications enabled + Global notifications enabled diff --git a/examples/qtmegachatapi/chatItemWidget.cpp b/examples/qtmegachatapi/chatItemWidget.cpp index 176867ff8..9b7a31e01 100644 --- a/examples/qtmegachatapi/chatItemWidget.cpp +++ b/examples/qtmegachatapi/chatItemWidget.cpp @@ -468,7 +468,7 @@ void ChatItemWidget::contextMenuEvent(QContextMenuEvent *event) if (notificationSettings) { actDoNotDisturb->setCheckable(true); - actDoNotDisturb->setChecked(!notificationSettings->isChatEnabled(mChatId)); + actDoNotDisturb->setChecked(notificationSettings->isChatDndEnabled(mChatId)); } else { diff --git a/examples/qtmegachatapi/chatMessage.cpp b/examples/qtmegachatapi/chatMessage.cpp index 830342913..8b3466571 100644 --- a/examples/qtmegachatapi/chatMessage.cpp +++ b/examples/qtmegachatapi/chatMessage.cpp @@ -36,12 +36,15 @@ ChatMessage::ChatMessage(ChatWindow *parent, megachat::MegaChatApi *mChatApi, me delete chatRoom; updateContent(); - mega::unique_ptr<::mega::MegaStringList> reactions(mChatWindow->mMegaChatApi->getMessageReactions(mChatId, mMessage->getMsgId())); - for (int i = 0; i < reactions->size(); i++) + if (mMessage->hasReactions()) { - int count = megaChatApi->getMessageReactionCount(mChatId, mMessage->getMsgId(), reactions->get(i)); - Reaction *reaction = new Reaction(this, reactions->get(i), count); - ui->mReactions->layout()->addWidget(reaction); // takes ownership + mega::unique_ptr<::mega::MegaStringList> reactions(mChatWindow->mMegaChatApi->getMessageReactions(mChatId, mMessage->getMsgId())); + for (int i = 0; i < reactions->size(); i++) + { + int count = megaChatApi->getMessageReactionCount(mChatId, mMessage->getMsgId(), reactions->get(i)); + Reaction *reaction = new Reaction(this, reactions->get(i), count); + ui->mReactions->layout()->addWidget(reaction); // takes ownership + } } connect(ui->mMsgDisplay, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onMessageCtxMenu(const QPoint&))); diff --git a/examples/qtmegachatapi/chatWindow.cpp b/examples/qtmegachatapi/chatWindow.cpp index fbc1ffb4a..12dc70df3 100644 --- a/examples/qtmegachatapi/chatWindow.cpp +++ b/examples/qtmegachatapi/chatWindow.cpp @@ -372,16 +372,21 @@ bool ChatWindow::eraseChatMessage(megachat::MegaChatMessage *msg, bool /*tempora megachat::MegaChatHandle msgId = getMessageId(msg); std::map::iterator itMessages; itMessages = mMsgsWidgetsMap.find(msgId); - if (itMessages != mMsgsWidgetsMap.end()) + if (itMessages == mMsgsWidgetsMap.end()) { - ChatMessage *auxMessage = itMessages->second; - int row = ui->mMessageList->row(auxMessage->getWidgetItem()); - QListWidgetItem *auxItem = ui->mMessageList->takeItem(row); - mMsgsWidgetsMap.erase(itMessages); - delete auxItem; - return true; + itMessages = mMsgsWidgetsMap.find(msg->getTempId()); + if (itMessages == mMsgsWidgetsMap.end()) + { + return false; + } } - return false; + + ChatMessage *auxMessage = itMessages->second; + int row = ui->mMessageList->row(auxMessage->getWidgetItem()); + QListWidgetItem *auxItem = ui->mMessageList->takeItem(row); + mMsgsWidgetsMap.erase(itMessages); + delete auxItem; + return true; } ChatMessage *ChatWindow::findChatMessage(megachat::MegaChatHandle msgId) @@ -886,7 +891,7 @@ void ChatWindow::createSettingsMenu(QMenu& menu) if (notificationSettings) { actDoNotDisturb->setCheckable(true); - actDoNotDisturb->setChecked(!notificationSettings->isChatEnabled(mChatRoom->getChatId())); + actDoNotDisturb->setChecked(notificationSettings->isChatDndEnabled(mChatRoom->getChatId())); } else { diff --git a/examples/qtmegachatapi/listItemController.cpp b/examples/qtmegachatapi/listItemController.cpp index a012c7e34..4e3567513 100644 --- a/examples/qtmegachatapi/listItemController.cpp +++ b/examples/qtmegachatapi/listItemController.cpp @@ -205,7 +205,7 @@ void ChatListItemController::onPushReceivedAndroid() void ChatListItemController::onMuteNotifications(bool enabled) { auto settings = mMainWindow->mApp->getNotificationSettings(); - if (settings && !settings->isChatEnabled(mItemId) != enabled) + if (settings && settings->isChatDndEnabled(mItemId) != enabled) { settings->enableChat(mItemId, !enabled); mMegaApi->setPushNotificationSettings(settings.get()); diff --git a/src/chatClient.cpp b/src/chatClient.cpp index cd8d924c8..1b0e64073 100644 --- a/src/chatClient.cpp +++ b/src/chatClient.cpp @@ -641,6 +641,22 @@ void Client::retryPendingConnections(bool disconnect, bool refreshURL) { mChatdClient->retryPendingConnections(disconnect, refreshURL); } + +#ifndef KARERE_DISABLE_WEBRTC + if (rtc && disconnect) + { + int index = 0; + while (mDnsCache.isValidUrl(TURNSERVER_SHARD - index) && index < MAX_TURN_SERVERS) + { + // invalidate IPs + mDnsCache.invalidateIps(TURNSERVER_SHARD - index); + index++; + } + + rtc->updateTurnServers(); + rtc->refreshTurnServerIp(); + } +#endif } promise::Promise Client::notifyUserStatus(bool background) @@ -3352,6 +3368,7 @@ void ChatRoom::onMsgOrderVerificationFail(const chatd::Message &msg, chatd::Idx void ChatRoom::onRecvNewMessage(chatd::Idx idx, chatd::Message& msg, chatd::Message::Status status) { + // truncate can be received as NEWMSG when the `msgid` is new for the client (later on the MSGUPD is also received) if ( (msg.type == chatd::Message::kMsgTruncate) // truncate received from a peer or from myself in another client || (msg.userid != parent.mKarereClient.myHandle() && status == chatd::Message::kNotSeen) ) // new (unseen) message received from a peer { @@ -3390,8 +3407,6 @@ void ChatRoom::onMessageEdited(const chatd::Message& msg, chatd::Idx idx) { chatd::Message::Status status = mChat->getMsgStatus(msg, idx); - //TODO: check a truncate always comes as an edit, even if no history exist at all (new chat) - // and, if so, remove the block from `onRecvNewMessage()` if ( (msg.type == chatd::Message::kMsgTruncate) // truncate received from a peer or from myself in another client || (msg.userid != parent.mKarereClient.myHandle() && status == chatd::Message::kNotSeen) ) // received message from a peer, still unseen, was edited / deleted { @@ -3401,7 +3416,8 @@ void ChatRoom::onMessageEdited(const chatd::Message& msg, chatd::Idx idx) void ChatRoom::onMessageStatusChange(chatd::Idx idx, chatd::Message::Status status, const chatd::Message& msg) { - if (msg.userid != parent.mKarereClient.myHandle() && status == chatd::Message::kSeen) // received message from a peer changed to seen + if (msg.userid != parent.mKarereClient.myHandle() + && status == chatd::Message::kSeen) // received message from a peer changed to seen { parent.mKarereClient.app.onChatNotification(mChatid, msg, status, idx); } diff --git a/src/chatClient.h b/src/chatClient.h index faad4fe52..781f5ab67 100644 --- a/src/chatClient.h +++ b/src/chatClient.h @@ -948,6 +948,7 @@ class Client: public ::mega::MegaGlobalListener, const std::string& myEmail() const { return mMyEmail; } uint64_t myIdentity() const { return mMyIdentity; } UserAttrCache& userAttrCache() const { return *mUserAttrCache; } + bool isUserAttrCacheReady() const { return mUserAttrCache.get(); } ConnState connState() const { return mConnState; } bool connected() const { return mConnState == kConnected; } diff --git a/src/chatd.cpp b/src/chatd.cpp index aec940922..a10035e96 100644 --- a/src/chatd.cpp +++ b/src/chatd.cpp @@ -2321,6 +2321,24 @@ void Connection::execCommand(const StaticBuffer& buf) chat.onPreviewersUpdate(count); break; } + case OP_MSGIDTIMESTAMP: + { + READ_ID(msgxid, 0); + READ_ID(msgid, 8); + READ_32(timestamp, 16); + CHATDS_LOG_DEBUG("recv MSGIDTIMESTAMP: '%s' -> '%s' %d", ID_CSTR(msgxid), ID_CSTR(msgid), timestamp); + mChatdClient.onMsgAlreadySent(msgxid, msgid); + break; + } + case OP_NEWMSGIDTIMESTAMP: + { + READ_ID(msgxid, 0); + READ_ID(msgid, 8); + READ_32(timestamp, 16); + CHATDS_LOG_DEBUG("recv NEWMSGIDTIMESTAMP: '%s' -> '%s' %d", ID_CSTR(msgxid), ID_CSTR(msgid), timestamp); + mChatdClient.msgConfirm(msgxid, msgid, timestamp); + break; + } default: { CHATDS_LOG_ERROR("Unknown opcode %d, ignoring all subsequent commands", opcode); @@ -3264,7 +3282,6 @@ Message* Chat::msgModify(Message& msg, const char* newdata, size_t newlen, void* SendingItem &item = it; if (item.msg->id() == msg.id()) { - item.msg->updated = age; item.msg->assign((void*)newdata, newlen); count++; } @@ -3294,7 +3311,22 @@ Message* Chat::msgModify(Message& msg, const char* newdata, size_t newlen, void* if (wptr.deleted()) return; + Id lastMsgId = mLastTextMsg.idx() == CHATD_IDX_INVALID + ? mLastTextMsg.xid() + : mLastTextMsg.id(); + postMsgToSending(upd->isSending() ? OP_MSGUPDX : OP_MSGUPD, upd, recipients); + if (lastMsgId == upd->id()) + { + if (upd->isValidLastMessage()) + { + onLastTextMsgUpdated(*upd, msgIndexFromId(upd->id())); + } + else //our last text msg is not valid anymore, find another one + { + findAndNotifyLastTextMsg(); + } + } }, mChatdClient.mKarereClient->appCtx); @@ -3449,7 +3481,16 @@ void Chat::onLastSeen(Id msgid, bool resend) return; } } - // else --> msgid was found locally + else // msgid was found locally + { + // if both `msgid` and `mLastSeenId` are known and localy available, there's an index to compare + if (mLastSeenIdx != CHATD_IDX_INVALID && idx < mLastSeenIdx) + { + CHATID_LOG_WARNING("onLastSeen: ignoring attempt to set last seen msgid backwards. Current: %s Attempt: %s", ID_CSTR(mLastSeenId), ID_CSTR(msgid)); + return; // `mLastSeenId` is newer than the received `msgid` + } + } + assert(mLastSeenId.isValid()); if (idx == mLastSeenIdx) @@ -3699,12 +3740,12 @@ Id Client::chatidFromPh(Id ph) return chatid; } -void Client::msgConfirm(Id msgxid, Id msgid) +void Client::msgConfirm(Id msgxid, Id msgid, uint32_t timestamp) { // TODO: maybe it's more efficient to keep a separate mapping of msgxid to messages? for (auto& chat: mChatForChatId) { - if (chat.second->msgConfirm(msgxid, msgid) != CHATD_IDX_INVALID) + if (chat.second->msgConfirm(msgxid, msgid, timestamp) != CHATD_IDX_INVALID) return; } CHATD_LOG_DEBUG("msgConfirm: No chat knows about message transaction id %s", ID_CSTR(msgxid)); @@ -3777,7 +3818,7 @@ Message* Chat::msgRemoveFromSending(Id msgxid, Id msgid) } // msgid can be 0 in case of rejections -Idx Chat::msgConfirm(Id msgxid, Id msgid) +Idx Chat::msgConfirm(Id msgxid, Id msgid, uint32_t timestamp) { Message* msg = msgRemoveFromSending(msgxid, msgid); if (!msg) @@ -3788,6 +3829,14 @@ Idx Chat::msgConfirm(Id msgxid, Id msgid) // update msgxid to msgid msg->setId(msgid, false); + bool tsUpdated = false; + if (timestamp != 0) + { + msg->ts = timestamp; + tsUpdated = true; + CHATID_LOG_DEBUG("Message timestamp has been updated in confirmation"); + } + // the keyid should be already confirmed by this time assert(!msg->isLocalKeyid()); @@ -3798,6 +3847,7 @@ Idx Chat::msgConfirm(Id msgxid, Id msgid) { mAttachmentNodes->addMessage(*msg, true, false); } + CALL_DB(addMsgToHistory, *msg, idx); assert(msg->backRefId); @@ -3825,7 +3875,7 @@ Idx Chat::msgConfirm(Id msgxid, Id msgid) CHATD_LOG_DEBUG("msgConfirm: updated opcode MSGUPDx to MSGUPD and the msgxid=%u to msgid=%u of %d message/s in the sending queue", msgxid, msgid, count); } - CALL_LISTENER(onMessageConfirmed, msgxid, *msg, idx); + CALL_LISTENER(onMessageConfirmed, msgxid, *msg, idx, tsUpdated); // if first message is own msg we need to init mNextHistFetchIdx to avoid loading own messages twice if (mNextHistFetchIdx == CHATD_IDX_INVALID && size() == 1) @@ -5343,6 +5393,8 @@ const char* Command::opcodeToStr(uint8_t code) RET_ENUM_NAME(NUMBYHANDLE); RET_ENUM_NAME(HANDLELEAVE); RET_ENUM_NAME(REACTIONSN); + RET_ENUM_NAME(MSGIDTIMESTAMP); + RET_ENUM_NAME(NEWMSGIDTIMESTAMP); default: return "(invalid opcode)"; }; } diff --git a/src/chatd.h b/src/chatd.h index 6a4e78f47..9b44afb21 100644 --- a/src/chatd.h +++ b/src/chatd.h @@ -150,8 +150,9 @@ class Listener * Normally the application doesn't need to care about it * @param msg - The message object - \c id() returns a real msgid, and \c isSending() is \c false * @param idx - The history buffer index at which the message was put + * @param tsUpdated - The ts has been updated or not */ - virtual void onMessageConfirmed(karere::Id /*msgxid*/, const Message& /*msg*/, Idx /*idx*/){} + virtual void onMessageConfirmed(karere::Id /*msgxid*/, const Message& /*msg*/, Idx /*idx*/, bool /*tsUpdated*/){} /** @brief A message was rejected by the server for some reason. * As the message is not yet in the history buffer, its \c id() @@ -852,7 +853,7 @@ class Chat: public karere::DeleteTrackable mForwardList.clear(); } // msgid can be 0 in case of rejections - Idx msgConfirm(karere::Id msgxid, karere::Id msgid); + Idx msgConfirm(karere::Id msgxid, karere::Id msgid, uint32_t timestamp = 0); bool msgAlreadySent(karere::Id msgxid, karere::Id msgid); Message* msgRemoveFromSending(karere::Id msgxid, karere::Id msgid); Idx msgIncoming(bool isNew, Message* msg, bool isLocal=false); @@ -1415,7 +1416,7 @@ class Client : public karere::DeleteTrackable void onKeepaliveSent(); bool onMsgAlreadySent(karere::Id msgxid, karere::Id msgid); - void msgConfirm(karere::Id msgxid, karere::Id msgid); + void msgConfirm(karere::Id msgxid, karere::Id msgid, uint32_t timestamp = 0); promise::Promise sendKeepalive(); void sendEcho(); @@ -1434,7 +1435,9 @@ class Client : public karere::DeleteTrackable // * Changes at CALLDATA protocol (new state) // - Version 6: // * Add commands ADDREACTION DELREACTION REACTIONSN - static const unsigned chatdVersion = 6; + // - Version 7: + // * Add commands MSGIDTIMESTAMP NEWMSGIDTIMESTAMP + static const unsigned chatdVersion = 7; Client(karere::Client *aKarereClient); ~Client(); diff --git a/src/chatdDb.h b/src/chatdDb.h index b2b2af62e..ae550def2 100644 --- a/src/chatdDb.h +++ b/src/chatdDb.h @@ -143,8 +143,8 @@ class ChatdSqliteDb: public chatd::DbInterface } virtual int updateSendingItemsContentAndDelta(const chatd::Message& msg) { - mDb.query("update sending set msg = ?, updated = ? where msgid = ? and chatid = ?", - msg, msg.updated, msg.id(), mChat.chatId()); + mDb.query("update sending set msg = ? where msgid = ? and chatid = ?", + msg, msg.id(), mChat.chatId()); return sqlite3_changes(mDb); } virtual void addMsgToHistory(const chatd::Message& msg, chatd::Idx idx) diff --git a/src/chatdMsg.h b/src/chatdMsg.h index 4ab266fe9..677f64af3 100644 --- a/src/chatdMsg.h +++ b/src/chatdMsg.h @@ -445,7 +445,27 @@ enum Opcode */ OP_REACTIONSN = 48, - OP_LAST = OP_HANDLELEAVE, + /** + * @brief + * S->C: The NEWMSG with msgxid had been written previously, informs of the permanent + * msgid. This command is similar to MSGID but furthermore update the ts sent in + * the NEWMSG due to incompatibility with the history + * + * Receive: + */ + OP_MSGIDTIMESTAMP = 49, + + /** + * @brief + * S->C: The NEWMSG with msgxid was successfully written and now has the permanent + * msgid. This command is similar to MSGID but furthermore update the ts sent in + * the NEWMSGID due to incompatibility with the history + * + * Receive: + */ + OP_NEWMSGIDTIMESTAMP = 50, + + OP_LAST = OP_NEWMSGIDTIMESTAMP, OP_INVALIDCODE = 0xFF }; diff --git a/src/megachatapi.h b/src/megachatapi.h index 4bc45a21b..65f43b8ee 100644 --- a/src/megachatapi.h +++ b/src/megachatapi.h @@ -1194,7 +1194,8 @@ class MegaChatMessage { CHANGE_TYPE_STATUS = 0x01, CHANGE_TYPE_CONTENT = 0x02, - CHANGE_TYPE_ACCESS = 0x04 /// When the access to attached nodes has changed (obsolete) + CHANGE_TYPE_ACCESS = 0x04, /// When the access to attached nodes has changed (obsolete) + CHANGE_TYPE_TIMESTAMP = 0x08, /// When ts has been updated by chatd in confirmation }; enum @@ -1561,6 +1562,9 @@ class MegaChatMessage * * - MegaChatMessage::CHANGE_TYPE_ACCESS = 0x04 * Check if the access to attached nodes has changed + * + * - MegaChatMessage::CHANGE_TYPE_TIMESTAMP = 0x08 + * Check if the ts has been updated by chatd */ virtual int getChanges() const; @@ -1583,6 +1587,9 @@ class MegaChatMessage * - MegaChatMessage::CHANGE_TYPE_ACCESS = 0x04 * Check if the access to attached nodes has changed * + * - MegaChatMessage::CHANGE_TYPE_TIMESTAMP = 0x08 + * Check if the ts has been updated by chatd + * * @return true if this message has an specific change */ virtual bool hasChanged(int changeType) const; diff --git a/src/megachatapi_impl.cpp b/src/megachatapi_impl.cpp index 0d296d04a..588da293b 100644 --- a/src/megachatapi_impl.cpp +++ b/src/megachatapi_impl.cpp @@ -1077,6 +1077,14 @@ void MegaChatApiImpl::sendPendingRequests() } case MegaChatRequest::TYPE_GET_FIRSTNAME: { + // if the app requested user attributes too early (ie. init with sid but without cache), + // the cache will not be ready yet. It needs to wait for fetchnodes to complete. + if (!mClient->isUserAttrCacheReady()) + { + errorCode = MegaChatError::ERROR_ACCESS; + break; + } + MegaChatHandle uh = request->getUserHandle(); const char* publicHandle = request->getLink(); MegaChatHandle ph = publicHandle ? karere::Id(publicHandle, strlen(publicHandle)).val : MEGACHAT_INVALID_HANDLE; @@ -1100,6 +1108,14 @@ void MegaChatApiImpl::sendPendingRequests() } case MegaChatRequest::TYPE_GET_LASTNAME: { + // if the app requested user attributes too early (ie. init with sid but without cache), + // the cache will not be ready yet. It needs to wait for fetchnodes to complete. + if (!mClient->isUserAttrCacheReady()) + { + errorCode = MegaChatError::ERROR_ACCESS; + break; + } + MegaChatHandle uh = request->getUserHandle(); const char* publicHandle = request->getLink(); MegaChatHandle ph = publicHandle ? karere::Id(publicHandle, strlen(publicHandle)).val : MEGACHAT_INVALID_HANDLE; @@ -1123,8 +1139,15 @@ void MegaChatApiImpl::sendPendingRequests() } case MegaChatRequest::TYPE_GET_EMAIL: { - MegaChatHandle uh = request->getUserHandle(); + // if the app requested user attributes too early (ie. init with sid but without cache), + // the cache will not be ready yet. It needs to wait for fetchnodes to complete. + if (!mClient->isUserAttrCacheReady()) + { + errorCode = MegaChatError::ERROR_ACCESS; + break; + } + MegaChatHandle uh = request->getUserHandle(); mClient->userAttrCache().getAttr(uh, karere::USER_ATTR_EMAIL) .then([request, this](Buffer *data) { @@ -1768,6 +1791,7 @@ void MegaChatApiImpl::sendPendingRequests() }); break; } + case MegaChatRequest::TYPE_IMPORT_MESSAGES: { if (mClient->initState() != karere::Client::kInitHasOfflineSession @@ -4551,8 +4575,9 @@ void MegaChatApiImpl::onInitStateChange(int newState) void MegaChatApiImpl::onChatNotification(karere::Id chatid, const Message &msg, Message::Status status, Idx idx) { - if (megaApi->isChatNotifiable(chatid)) - { + if (megaApi->isChatNotifiable(chatid) // filtering based on push-notification settings + && !msg.isEncrypted()) // avoid msgs to be notified when marked as "seen", but still decrypting + { MegaChatMessagePrivate *message = new MegaChatMessagePrivate(msg, status, idx); fireOnChatNotification(chatid, message); } @@ -6211,11 +6236,15 @@ void MegaChatRoomHandler::onUnsentEditLoaded(chatd::Message &msg, bool oriMsgIsS fireOnMessageLoaded(message); } -void MegaChatRoomHandler::onMessageConfirmed(Id msgxid, const Message &msg, Idx idx) +void MegaChatRoomHandler::onMessageConfirmed(Id msgxid, const Message &msg, Idx idx, bool tsUpdated) { MegaChatMessagePrivate *message = new MegaChatMessagePrivate(msg, Message::kServerReceived, idx); message->setStatus(MegaChatMessage::STATUS_SERVER_RECEIVED); message->setTempId(msgxid); // to allow the app to find the "temporal" message + if (tsUpdated) + { + message->setTsUpdated(); + } std::set *msgToUpdate = handleNewMessage(message); @@ -6250,7 +6279,10 @@ void MegaChatRoomHandler::onMessageStatusChange(Idx idx, Message::Status status, message->setStatus(status); fireOnMessageUpdate(message); - if (megaApi->isChatNotifiable(chatid) && msg.userid != chatApi->getMyUserHandle() && status == chatd::Message::kSeen) // received message from a peer changed to seen + if (megaApi->isChatNotifiable(chatid) + && msg.userid != chatApi->getMyUserHandle() + && status == chatd::Message::kSeen // received message from a peer changed to seen + && !msg.isEncrypted()) // messages can be "seen" while being decrypted { MegaChatMessagePrivate *message = new MegaChatMessagePrivate(msg, status, idx); chatApiImpl->fireOnChatNotification(chatid, message); @@ -6410,10 +6442,12 @@ const char* MegaChatErrorPrivate::getGenericErrorString(int errorCode) return "No error"; case ERROR_ARGS: return "Invalid argument"; + case ERROR_TOOMANY: + return "Too many uses for this resource"; case ERROR_ACCESS: return "Access denied"; case ERROR_NOENT: - return "Resouce does not exist"; + return "Resource does not exist"; case ERROR_EXIST: return "Resource already exists"; case ERROR_UNKNOWN: @@ -7725,6 +7759,11 @@ void MegaChatMessagePrivate::setAccess() this->changed |= MegaChatMessage::CHANGE_TYPE_ACCESS; } +void MegaChatMessagePrivate::setTsUpdated() +{ + this->changed |= MegaChatMessage::CHANGE_TYPE_TIMESTAMP; +} + int MegaChatMessagePrivate::convertEndCallTermCodeToUI(const Message::CallEndedInfo &callEndInfo) { int code; diff --git a/src/megachatapi_impl.h b/src/megachatapi_impl.h index 2bf23ae17..0cc86e4e6 100644 --- a/src/megachatapi_impl.h +++ b/src/megachatapi_impl.h @@ -486,7 +486,7 @@ class MegaChatRoomHandler :public karere::IApp::IChatHandler virtual void onHistoryDone(chatd::HistSource source); virtual void onUnsentMsgLoaded(chatd::Message& msg); virtual void onUnsentEditLoaded(chatd::Message& msg, bool oriMsgIsSending); - virtual void onMessageConfirmed(karere::Id msgxid, const chatd::Message& msg, chatd::Idx idx); + virtual void onMessageConfirmed(karere::Id msgxid, const chatd::Message& msg, chatd::Idx idx, bool tsUpdated); virtual void onMessageRejected(const chatd::Message& msg, uint8_t reason); virtual void onMessageStatusChange(chatd::Idx idx, chatd::Message::Status newStatus, const chatd::Message& msg); virtual void onMessageEdited(const chatd::Message& msg, chatd::Idx idx); @@ -850,6 +850,7 @@ class MegaChatMessagePrivate : public MegaChatMessage void setContentChanged(); void setCode(int code); void setAccess(); + void setTsUpdated(); static int convertEndCallTermCodeToUI(const chatd::Message::CallEndedInfo &callEndInfo); diff --git a/src/net/libwebsocketsIO.cpp b/src/net/libwebsocketsIO.cpp index 0e6049d2d..66728885c 100644 --- a/src/net/libwebsocketsIO.cpp +++ b/src/net/libwebsocketsIO.cpp @@ -38,7 +38,8 @@ LibwebsocketsIO::LibwebsocketsIO(Mutex &mutex, ::mega::Waiter* waiter, ::mega::M if (!libuvWaiter) { WEBSOCKETS_LOG_ERROR("Fatal error: NULL or invalid waiter object"); - exit(0); + assert(false); + abort(); } eventloop = libuvWaiter->eventloop; lws_uv_initloop(wscontext, libuvWaiter->eventloop, 0); diff --git a/src/net/websocketsIO.cpp b/src/net/websocketsIO.cpp index 88dd9faa9..7df1f2b23 100644 --- a/src/net/websocketsIO.cpp +++ b/src/net/websocketsIO.cpp @@ -299,6 +299,11 @@ bool DNScache::setIp(int shard, std::string ipv4, std::string ipv6) return false; } +bool DNScache::invalidateIps(int shard) +{ + return setIp(shard, "", ""); +} + bool DNScache::getIp(int shard, std::string &ipv4, std::string &ipv6) { auto it = mRecords.find(shard); diff --git a/src/net/websocketsIO.h b/src/net/websocketsIO.h index 080f555da..d23c13ac0 100644 --- a/src/net/websocketsIO.h +++ b/src/net/websocketsIO.h @@ -37,6 +37,7 @@ class DNScache // the record for the given shard must exist (to load from DB) bool setIp(int shard, std::string ipv4, std::string ipv6); bool getIp(int shard, std::string &ipv4, std::string &ipv6); + bool invalidateIps(int shard); void connectDone(int shard, const std::string &ip); bool isMatch(int shard, const std::vector &ipsv4, const std::vector &ipsv6); bool isMatch(int shard, const std::string &ipv4, const std::string &ipv6); diff --git a/src/rtcModule/webrtc.cpp b/src/rtcModule/webrtc.cpp index 3db49f804..ce1f6589d 100644 --- a/src/rtcModule/webrtc.cpp +++ b/src/rtcModule/webrtc.cpp @@ -69,7 +69,7 @@ RtMessage::RtMessage(chatd::Chat &aChat, const StaticBuffer& msg) RtcModule::RtcModule(karere::Client& client, IGlobalHandler& handler, IRtcCrypto* crypto, const char* iceServers) : IRtcModule(client, handler, crypto, crypto->anonymizeId(client.myHandle())), - mStaticIceSever(iceServers), + mStaticIceServers(iceServers), mIceServerProvider(client.api, "turn"), mManager(*this) { @@ -87,15 +87,66 @@ RtcModule::RtcModule(karere::Client& client, IGlobalHandler& handler, void RtcModule::init() { - StaticProvider iceServerStatic(mStaticIceSever); - setIceServers(iceServerStatic); + // set cached ICE servers, or static/hardcoded if not available + StaticProvider iceServersStatic; + string jsonCachedTurnServers = getCachedTurnServers(); + if (jsonCachedTurnServers.size()) + { + iceServersStatic.setServers(jsonCachedTurnServers.c_str()); + } + else + { + iceServersStatic.setServers(mStaticIceServers); + } + setIceServers(iceServersStatic); + + // fetch ICE servers URLs from Load Balancer (GeLB) auto wptr = weakHandle(); mIceServerProvider.fetchServers() .then([wptr, this]() { if (wptr.deleted()) return; - setIceServers(mIceServerProvider); + + // replace cache ICE servers URLs with received ones + int shard = TURNSERVER_SHARD; + for (const std::shared_ptr& serverInfo : mIceServerProvider) + { + std::string fullUrl = serverInfo->url; + if (fullUrl.size()) + { + // Example: "turn:example.url.co:3478?transport=udp" + size_t posInitialColon = fullUrl.find(":") + 1; + std::string urlString = fullUrl.substr(posInitialColon, fullUrl.size() - posInitialColon); + karere::Url url(urlString); + + if (!mKarereClient.mDnsCache.isValidUrl(shard) || url != mKarereClient.mDnsCache.getUrl(shard)) + { + if (mKarereClient.mDnsCache.isValidUrl(shard)) + { + mKarereClient.mDnsCache.removeRecord(shard); + } + + mKarereClient.mDnsCache.addRecord(shard, urlString); + } + + shard--; + } + } + + // discard any obsolete cached URL (not returned by GeLB) + int maxShard = TURNSERVER_SHARD - MAX_TURN_SERVERS; + while (mKarereClient.mDnsCache.isValidUrl(shard) && shard > maxShard) + { + mKarereClient.mDnsCache.removeRecord(shard); + shard--; + } + + // finally, update the IPs corresponding to the received URLs + if (mIceServerProvider.size()) + { + refreshTurnServerIp(); + } }) .fail([](const ::promise::Error& err) { @@ -152,6 +203,75 @@ void RtcModule::removeCallRetry(karere::Id chatid, bool retry) mRetryCall.erase(chatid); } +string RtcModule::getCachedTurnServers() +{ + int i = 0; + vector urls; + while (mKarereClient.mDnsCache.isValidUrl(TURNSERVER_SHARD - i) && i < MAX_TURN_SERVERS) + { + string ipv4; + string ipv6; + karere::Url turnServerUrl = mKarereClient.mDnsCache.getUrl(TURNSERVER_SHARD - i); + if (mKarereClient.mDnsCache.getIp(TURNSERVER_SHARD - i, ipv4, ipv6)) + { + if (ipv4.size()) + { + urls.push_back(buildTurnServerUrl(ipv4, turnServerUrl.port, turnServerUrl.path)); + } + + if(ipv6.size()) + { + urls.push_back(buildTurnServerUrl(ipv6, turnServerUrl.port, turnServerUrl.path)); + } + } + else // no cached IP available for this URL + { + urls.push_back(buildTurnServerUrl(turnServerUrl.host, turnServerUrl.port, turnServerUrl.path)); + + } + i++; + } + + string json; + if (urls.size()) + { + json.insert(json.begin(), '['); + for (const string& url : urls) + { + if (json.size() > 2) + { + json.append(", "); + } + + json.append("{\"host\":\"") + .append(url) + .append("\"}"); + } + + json.append("]"); + } + + return json; +} + +string RtcModule::buildTurnServerUrl(const string &host, int port, const string &path) const +{ + string url("turn:"); + url.append(host); + if (port > 0) + { + url.append(":") + .append(std::to_string(port)); + } + + if (path.size()) + { + url.append(path); + } + + return url; +} + bool RtcModule::selectAudioInDevice(const string &devname) { return false; @@ -867,6 +987,79 @@ void RtcModule::abortCallRetry(Id chatid) removeCallWithoutParticipants(chatid); } +void RtcModule::refreshTurnServerIp() +{ + if (mIceServerProvider.busy()) + { + RTCM_LOG_WARNING("Turn server URLs not available yet. Fetching..."); + return; + } + + mDnsRequestId++; + + int index = 0; + while (mKarereClient.mDnsCache.isValidUrl(TURNSERVER_SHARD - index) && index < MAX_TURN_SERVERS) + { + int shard = TURNSERVER_SHARD - index; + std::string host = mKarereClient.mDnsCache.getUrl(shard).host; + unsigned int dnsRequestId = mDnsRequestId; // capture the value for the lambda + auto wptr = weakHandle(); + mDnsResolver.wsResolveDNS(mKarereClient.websocketIO, host.c_str(), + [wptr, this, shard, dnsRequestId] + (int statusDNS, const std::vector &ipsv4, const std::vector &ipsv6) + { + if (wptr.deleted()) + return; + + if (mKarereClient.isTerminated()) + { + RTCM_LOG_ERROR("DNS resolution completed but karere client was terminated."); + return; + } + + if (dnsRequestId != mDnsRequestId) + { + RTCM_LOG_ERROR("DNS resolution completed but ignored: a newer attempt is already started (old: %d, new: %d)", + dnsRequestId, mDnsRequestId); + return; + } + + if (statusDNS < 0) + { + RTCM_LOG_ERROR("Async DNS error in rtcModule. Error code: %d", statusDNS); + } + + assert(mKarereClient.mDnsCache.hasRecord(shard)); + + if (statusDNS >= 0 && (ipsv4.size() || ipsv6.size())) + { + RTCM_LOG_ERROR("New IP for TURN servers: ipv4 - %s ipv6 - %s", + ipsv4.size() ? ipsv4[0].c_str() : "", + ipsv6.size() ? ipsv6[0].c_str() : ""); + mKarereClient.mDnsCache.setIp(shard, ipsv4, ipsv6); + + } + else + { + mKarereClient.mDnsCache.invalidateIps(shard); + } + + updateTurnServers(); + + }); + + index++; + } +} + +void RtcModule::updateTurnServers() +{ + StaticProvider iceServersProvider; + string jsonCachedTurnServers = getCachedTurnServers(); + iceServersProvider.setServers(jsonCachedTurnServers.c_str()); + setIceServers(iceServersProvider); +} + void RtcModule::onKickedFromChatRoom(Id chatid) { auto callIt = mCalls.find(chatid); @@ -904,7 +1097,6 @@ void RtcModule::onKickedFromChatRoom(Id chatid) removeCallRetry(chatid, false); removeCallWithoutParticipants(chatid); } - } uint32_t RtcModule::clientidFromPeer(karere::Id chatid, Id userid) @@ -1818,7 +2010,7 @@ void Call::removeSession(Session& sess, TermCode reason) } TermCode terminationCode = (TermCode)(reason & ~TermCode::kPeer); - if (terminationCode == TermCode::kErrIceFail || terminationCode == TermCode::kErrIceTimeout) + if (terminationCode == TermCode::kErrIceFail || terminationCode == TermCode::kErrIceTimeout || terminationCode == kErrSessSetupTimeout) { if (mIceFails.find(endpointId) == mIceFails.end()) { @@ -2856,11 +3048,12 @@ void Session::createRtcConn() if (mCall.mIceFails.find(endPoint) != mCall.mIceFails.end()) { RTCM_LOG_ERROR("Using ALL ICE servers, because ICE to this peer has failed %d times", mCall.mIceFails[endPoint]); - StaticProvider iceServerStatic(mCall.mManager.mStaticIceSever); - mCall.mManager.addIceServers(iceServerStatic); + StaticProvider iceServerStatic(mCall.mManager.mStaticIceServers); + mCall.mManager.setIceServers(iceServerStatic); } mRtcConn = artc::myPeerConnection(mCall.mManager.mIceServers, *this); + RTCM_LOG_INFO("Create RTC connection ICE server: %s", mCall.mManager.mIceServers[0].uri.c_str()); if (mCall.mLocalStream) { std::vector vector; @@ -3097,6 +3290,8 @@ void Session::onIceConnectionChange(webrtc::PeerConnectionInterface::IceConnecti mTsIceConn = time(NULL); mAudioPacketLostAverage = 0; mCall.notifySessionConnected(*this); + EndpointId endpointId(mPeer, mPeerClient); + mCall.mIceFails.erase(endpointId); } } diff --git a/src/rtcModule/webrtc.h b/src/rtcModule/webrtc.h index 59c798e68..75aebdc38 100644 --- a/src/rtcModule/webrtc.h +++ b/src/rtcModule/webrtc.h @@ -46,6 +46,9 @@ const uint8_t kCallDataRinging = 1; #define CHATSTATS_PORT 0 +#define TURNSERVER_SHARD -10 // shard number in the DNS cache for TURN servers +#define MAX_TURN_SERVERS 5 // max. number of TURN servers to be managed + namespace chatd { class Connection; @@ -440,6 +443,8 @@ class IRtcModule: public karere::DeleteTrackable virtual int numCalls() const = 0; virtual std::vector chatsWithCall() const = 0; virtual void abortCallRetry(karere::Id chatid) = 0; + virtual void refreshTurnServerIp() = 0; + virtual void updateTurnServers() = 0; }; IRtcModule* create(karere::Client& client, IGlobalHandler& handler, IRtcCrypto* crypto, const char* iceServers); diff --git a/src/rtcModule/webrtcPrivate.h b/src/rtcModule/webrtcPrivate.h index 3b2924a84..84abee6b7 100644 --- a/src/rtcModule/webrtcPrivate.h +++ b/src/rtcModule/webrtcPrivate.h @@ -303,6 +303,30 @@ class Call: public ICall void changeVideoInDevice(); }; +/* + * Partial implementation of the WebsocketsClient, just for the purpose of + * resolving the IPs behind the ICE servers in order to be added to cache. + */ +class DnsResolver : public WebsocketsClient +{ +public: + DnsResolver() {} + virtual ~DnsResolver() {} + + bool wsConnect(WebsocketsIO *websocketIO, const char *ip, + const char *host, int port, const char *path, bool ssl) = delete; + int wsGetNoNameErrorCode(WebsocketsIO *websocketIO) = delete; + bool wsSendMessage(char *msg, size_t len) = delete; // returns true on success, false if error + void wsDisconnect(bool immediate) = delete; + bool wsIsConnected() = delete; + void wsCloseCbPrivate(int errcode, int errtype, const char *preason, size_t reason_len) = delete; + + void wsConnectCb() override {} + void wsCloseCb(int errcode, int errtype, const char *preason, size_t /*preason_len*/) override {} + void wsHandleMsgCb(char *data, size_t len) override {} + void wsSendMsgCb(const char *, size_t) override {} +}; + class RtcModule: public IRtcModule, public chatd::IRtcHandler { public: @@ -364,6 +388,8 @@ class RtcModule: public IRtcModule, public chatd::IRtcHandler virtual int numCalls() const; virtual std::vector chatsWithCall() const; virtual void abortCallRetry(karere::Id chatid); + void refreshTurnServerIp() override; + void updateTurnServers() override; //== void updatePeerAvState(karere::Id chatid, karere::Id callid, karere::Id userid, uint32_t clientid, karere::AvFlags av); void handleCallDataRequest(chatd::Chat &chat, karere::Id userid, uint32_t clientid, karere::Id callid, karere::AvFlags avFlagsRemote); @@ -377,7 +403,7 @@ class RtcModule: public IRtcModule, public chatd::IRtcHandler void launchCallRetry(karere::Id chatid, karere::AvFlags av, bool isActiveRetry = true); virtual ~RtcModule(); protected: - const char* mStaticIceSever; + const char* mStaticIceServers; karere::GelbProvider mIceServerProvider; webrtc::PeerConnectionInterface::IceServers mIceServers; std::map> mCalls; @@ -386,6 +412,10 @@ class RtcModule: public IRtcModule, public chatd::IRtcHandler RtcModule &mManager; std::map mRetryCallTimers; std::string mVideoDeviceSelected; + + DnsResolver mDnsResolver; + unsigned int mDnsRequestId = 0; + IRtcCrypto& crypto() const { return *mCrypto; } template void cmdEndpoint(chatd::Chat &chat, uint8_t type, karere::Id chatid, karere::Id userid, uint32_t clientid, Args... args); @@ -401,6 +431,10 @@ class RtcModule: public IRtcModule, public chatd::IRtcHandler void removeCallRetry(karere::Id chatid, bool retry = true); std::shared_ptr mWebRtcLogger; + + std::string getCachedTurnServers(); + std::string buildTurnServerUrl(const std::string& host, int port, const std::string& path) const; + friend class Call; friend class Session; public: @@ -476,6 +510,7 @@ class RtMessageComposer: public chatd::Command updateLenField(); } }; + } #endif diff --git a/src/serverListProvider.h b/src/serverListProvider.h index ac027ce80..1f6fcaef6 100644 --- a/src/serverListProvider.h +++ b/src/serverListProvider.h @@ -57,6 +57,15 @@ struct TurnServerInfo url+=+":"; url+=std::to_string(port); } + + const char* transport = nullptr; + SRVJSON_GET_OPTIONAL_PROP(transport, transport, String); + if (transport) + { + url+=+"?"; + url+="transport="; + url+=transport; + } } SRVJSON_GET_OPTIONAL_PROP(user, user, String); SRVJSON_GET_OPTIONAL_PROP(pass, pass, String); @@ -105,7 +114,16 @@ class StaticProvider: public ListProvider { protected: public: + StaticProvider() + { + } + StaticProvider(const char* serversJson) + { + setServers(serversJson); + } + + void setServers(const char* serversJson) { rapidjson::Document doc; doc.Parse(serversJson); @@ -120,7 +138,6 @@ class StaticProvider: public ListProvider { KR_LOG_ERROR("Error to extract server form static ice-server list"); } - } }; @@ -198,7 +215,14 @@ class GelbProvider: public ListProvider, public DeleteTrackable : mApi(api), mService(service) { } + + // true if still fetching servers from Gelb + bool busy() const + { + return mBusy; + } }; + } #endif diff --git a/src/url.cpp b/src/url.cpp index 933c43f85..1aeefd90d 100644 --- a/src/url.cpp +++ b/src/url.cpp @@ -66,6 +66,11 @@ void Url::parse(const std::string& url) throw std::runtime_error("Url::parse: Invalid URL '"+url+"', host is empty"); } +bool Url::operator!=(const Url &url) const +{ + return host != url.host || protocol != url.protocol || port != url.port || path != url.path; +} + uint16_t Url::getPortFromProtocol() const { if ((protocol == "http") || (protocol == "ws")) diff --git a/src/url.h b/src/url.h index 5f7da1108..493331ed7 100644 --- a/src/url.h +++ b/src/url.h @@ -19,6 +19,7 @@ class Url Url(): isSecure(false) {} void parse(const std::string& url); bool isValid() const { return !host.empty(); } + bool operator!=(const Url& url) const; }; } diff --git a/tests/sdk_test/sdk_test.cpp b/tests/sdk_test/sdk_test.cpp index 6e83b060d..9bbd56907 100644 --- a/tests/sdk_test/sdk_test.cpp +++ b/tests/sdk_test/sdk_test.cpp @@ -4579,7 +4579,7 @@ void TestChatRoomListener::onMessageUpdate(MegaChatApi *api, MegaChatMessage *ms msgId[apiIndex].push_back(msg->getMsgId()); - if (msg->getChanges() == MegaChatMessage::CHANGE_TYPE_STATUS) + if (msg->hasChanged(MegaChatMessage::CHANGE_TYPE_STATUS)) { if (msg->getStatus() == MegaChatMessage::STATUS_SERVER_RECEIVED) { @@ -4592,7 +4592,7 @@ void TestChatRoomListener::onMessageUpdate(MegaChatApi *api, MegaChatMessage *ms } } - if (msg->getChanges() == MegaChatMessage::CHANGE_TYPE_CONTENT && msg->isEdited()) + if (msg->hasChanged(MegaChatMessage::CHANGE_TYPE_CONTENT) && msg->isEdited()) { mEditedMessageHandle[apiIndex] = msg->getMsgId(); msgEdited[apiIndex] = true;