// Copyright Epic Games, Inc. All Rights Reserved. #include "XmppStrophe/XmppMultiUserChatStrophe.h" #include "XmppStrophe/XmppConnectionStrophe.h" #include "XmppStrophe/XmppStrophe.h" #include "XmppStrophe/StropheStanza.h" #include "XmppStrophe/StropheStanzaConstants.h" #include "XmppLog.h" #include "Logging/LogScopedVerbosityOverride.h" #include "Misc/Guid.h" #include "Misc/EmbeddedCommunication.h" #include "Containers/BackgroundableTicker.h" #include "Stats/Stats.h" #if WITH_XMPP_STROPHE #define TickRequesterId FName("StropheMultichat") FXmppMultiUserChatStrophe::FXmppMultiUserChatStrophe(FXmppConnectionStrophe& InConnectionManager) : FTSTickerObjectBase(0.0f, FTSBackgroundableTicker::GetCoreTicker()) , ConnectionManager(InConnectionManager) { } FXmppMultiUserChatStrophe::~FXmppMultiUserChatStrophe() { CleanupMessages(); } void FXmppMultiUserChatStrophe::OnDisconnect() { CleanupMessages(); Chatrooms.Empty(); PendingRoomCreateConfigs.Empty(); } void FXmppMultiUserChatStrophe::OnReconnect() { } bool FXmppMultiUserChatStrophe::ReceiveStanza(const FStropheStanza& IncomingStanza) { // MUC presence are from our MUC domain const FString StanzaName = IncomingStanza.GetName(); if (StanzaName == Strophe::SN_PRESENCE && IncomingStanza.GetFrom().Domain.Equals(ConnectionManager.GetMucDomain(), ESearchCase::CaseSensitive)) { if (IncomingStanza.GetType() != Strophe::ST_ERROR) { return HandlePresenceStanza(IncomingStanza); } else { return HandlePresenceErrorStanza(IncomingStanza); } } else if (StanzaName == Strophe::SN_MESSAGE) { const FString StanzaType = IncomingStanza.GetType(); if (StanzaType == Strophe::ST_GROUPCHAT) { return HandleGroupChatStanza(IncomingStanza); } else if (StanzaType == Strophe::ST_ERROR) { return HandleGroupChatErrorStanza(IncomingStanza); } } else if (StanzaName == Strophe::SN_IQ) { // Ignore pings if (IncomingStanza.HasChildByNameAndNamespace(Strophe::SN_PING, Strophe::SNS_PING)) { return false; } // Config sets/gets (what we're caring about here) don't have queries in the "muc owner" namespace, so filter out those who do TOptional QueryStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_QUERY, Strophe::SNS_MUC_OWNER); if (!QueryStanza.IsSet()) { const FString StanzaType = IncomingStanza.GetType(); if (StanzaType == Strophe::ST_RESULT) { // If this is a result type, check the FromJid for JUST a domain (i.e. the server), and drop it if it is // These are replies to pings (pongs), and until we keep track of what IDs we use for sent-pings, this filter // method will have to do const FXmppUserJid FromUser(IncomingStanza.GetFrom()); const bool bMessageFromOnlyDomain = FromUser.Id.IsEmpty() && !FromUser.Domain.IsEmpty() && FromUser.Resource.IsEmpty(); if (bMessageFromOnlyDomain) { // Ignore pongs return false; } } if (StanzaType != Strophe::ST_ERROR) { return HandleRoomConfigStanza(IncomingStanza); } else { return HandleRoomConfigErrorStanza(IncomingStanza); } } } return false; } bool FXmppMultiUserChatStrophe::HandlePresenceStanza(const FStropheStanza& IncomingStanza) { FXmppMucPresence Presence; Presence.bIsAvailable = IncomingStanza.GetType() != Strophe::ST_UNAVAILABLE; Presence.UserJid = IncomingStanza.GetFrom(); TOptional UserStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_X, Strophe::SNS_MUC_USER); if (UserStanza.IsSet()) { TOptional UserItemStanza = UserStanza->GetChildStropheStanza(Strophe::SN_ITEM); if (UserItemStanza.IsSet()) { Presence.MemberJid = FXmppStrophe::JidFromString(UserItemStanza->GetAttribute(Strophe::SA_JID)); Presence.Role = UserItemStanza->GetAttribute(Strophe::SA_ROLE); Presence.Affiliation = UserItemStanza->GetAttribute(Strophe::SA_AFFILIATION); } } FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingMucPresenceUpdates.Enqueue(MakeUnique(MoveTemp(Presence))); return true; } bool FXmppMultiUserChatStrophe::HandlePresenceErrorStanza(const FStropheStanza& IncomingStanza) { FXmppStropheErrorPair OutError; OutError.RoomId = IncomingStanza.GetFrom().Id; TOptional Error = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR); if (Error.IsSet()) { const TArray ErrorList = Error->GetChildren(); for (const FStropheStanza& ErrorItem : ErrorList) { const FString ErrorName = ErrorItem.GetName(); if (ErrorName == Strophe::SN_NOT_AUTHORIZED) { OutError.ErrorMessage += TEXT("A password is required to join this room"); } else if (ErrorName == Strophe::SN_FORBIDDEN) { OutError.ErrorMessage += TEXT("You are not allowed to join this room"); } else if (ErrorName == Strophe::SN_ITEM_NOT_FOUND) { OutError.ErrorMessage += TEXT("That room does not exist"); } else if (ErrorName == Strophe::SN_NOT_ALLOWED) { OutError.ErrorMessage += TEXT("You are unable to create rooms"); } else if (ErrorName == Strophe::SN_NOT_ACCEPTABLE) { OutError.ErrorMessage += TEXT("You may not change your nickname"); } else if (ErrorName == Strophe::SN_REGISTRATION_REQUIRED) { OutError.ErrorMessage += TEXT("You are not a member of this room"); } else if (ErrorName == Strophe::SN_CONFLICT) { OutError.ErrorMessage += TEXT("Your nickname is already in use in this room"); } else if (ErrorName == Strophe::SN_SERVICE_UNAVAILABLE) { OutError.ErrorMessage += TEXT("The requested room is full"); } else { OutError.ErrorMessage += FString::Printf(TEXT("Unknown Error %s. "), *ErrorName); } } } if (OutError.ErrorMessage.IsEmpty()) { OutError.ErrorMessage = TEXT("Unknown error"); } UE_LOG(LogXmpp, Warning, TEXT("MUC: Received error %s"), *OutError.ErrorMessage); if (!OutError.ErrorMessage.IsEmpty()) { FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingMucPresenceErrors.Enqueue(MoveTemp(OutError)); } return true; } bool FXmppMultiUserChatStrophe::HandleGroupChatStanza(const FStropheStanza& IncomingStanza) { TOptional SubjectStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_SUBJECT); if (SubjectStanza.IsSet()) { FXmppStropheSubjectUpdate SubjectUpdate; SubjectUpdate.NewSubject = SubjectStanza->GetText(); SubjectUpdate.RoomId = IncomingStanza.GetFrom().Id; FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingRoomSubjects.Enqueue(MoveTemp(SubjectUpdate)); return true; } // Check for room settings update (status code 104) TOptional XStanza = IncomingStanza.GetChildByNameAndNamespace(Strophe::SN_X, Strophe::SNS_MUC_USER); if (XStanza.IsSet()) { // We're looking for exactly 1 'status' child const TArray XChildren = XStanza->GetChildren(); if (XChildren.Num() == 1) { const FStropheStanza& XChild = XChildren[0]; if (XChild.GetName() == Strophe::SN_STATUS) { FString Code = XChild.GetAttribute(Strophe::SA_CODE); if (Code == Strophe::SC_104) { FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingRoomInfoUpdates.Enqueue(FXmppRoomId(IncomingStanza.GetFrom().Id)); } } } return true; } TOptional BodyText = IncomingStanza.GetBodyText(); if (!BodyText.IsSet()) { // Bad data, no body return true; } FXmppChatMessage ChatMessage; ChatMessage.ToJid = IncomingStanza.GetTo(); ChatMessage.FromJid = IncomingStanza.GetFrom(); ChatMessage.Body = MoveTemp(BodyText.GetValue()); // Parse Timezone TOptional StanzaDelay = IncomingStanza.GetChildStropheStanza(Strophe::SN_DELAY); if (StanzaDelay.IsSet()) { if (StanzaDelay->HasAttribute(Strophe::SA_STAMP)) { FString Timestamp = StanzaDelay->GetAttribute(Strophe::SA_STAMP); FDateTime::ParseIso8601(*Timestamp, ChatMessage.Timestamp); } } if (ChatMessage.Timestamp == 0) { ChatMessage.Timestamp = FDateTime::UtcNow(); } FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingGroupChatMessages.Enqueue(MakeUnique(MoveTemp(ChatMessage))); return true; } bool FXmppMultiUserChatStrophe::HandleGroupChatErrorStanza(const FStropheStanza& IncomingStanza) { FString ErrorMessage; TOptional Error = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR); if (Error.IsSet()) { const TArray ErrorList = Error->GetChildren(); for (const FStropheStanza& ErrorItem : ErrorList) { const FString ErrorName = ErrorItem.GetName(); if (ErrorName == Strophe::SN_FORBIDDEN) { ErrorMessage += TEXT("Unable to send message to room. "); } else if (ErrorName == Strophe::SN_BAD_REQUEST) { ErrorMessage += TEXT("Unable to send groupchat message to an individual. "); } else if (ErrorName == Strophe::SN_NOT_ACCEPTABLE) { ErrorMessage += TEXT("You may not send messages to rooms you have not joined. "); } else { ErrorMessage += FString::Printf(TEXT("%s. "), *ErrorName); } } } if (ErrorMessage.IsEmpty()) { ErrorMessage = TEXT("Unknown error"); } UE_LOG(LogXmpp, Warning, TEXT("MUC: Received GroupChat error %s"), *ErrorMessage); return true; } bool FXmppMultiUserChatStrophe::HandleRoomConfigStanza(const FStropheStanza& IncomingStanza) { // There are four possible outputs from this that mean success: // a) No children in iq stanza; this means we successfully set the configuration // for the channel! // b) The iq stanza has a query stanza with no children; this means we requested // the config options or the config option defaults and there are none // c) The query stanza has children, but those childen have no value children; // this means we got the list of possible config options // d) The query stanza has children and those children have value stanzas; this // means the channel already exists and we're querying the options for it // Check for config write case (No Query child) TOptional QueryStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_QUERY); if (!QueryStanza.IsSet()) { FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingRoomConfigWriteSuccesses.Enqueue(IncomingStanza.GetFrom().Id); return true; } // Right now we only care about the successful write case, but you could totally // write a config parser here and pass back the config values for a room to the // game thread if you wanted. return true; } bool FXmppMultiUserChatStrophe::HandleRoomConfigErrorStanza(const FStropheStanza& IncomingStanza) { FXmppStropheErrorPair OutError; OutError.RoomId = IncomingStanza.GetFrom().Id; TOptional ErrorStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_ERROR); if (ErrorStanza.IsSet()) { const FString ErrorType = ErrorStanza->GetType(); if (ErrorType == Strophe::ST_AUTH && ErrorStanza->HasChild(Strophe::SN_FORBIDDEN)) { OutError.ErrorMessage = TEXT("Only the room owner may modify the room configuration"); } // Don't log the error message, as we may not care about failures depending on the CallbackType } if (!OutError.ErrorMessage.IsEmpty()) { FEmbeddedCommunication::KeepAwake(TickRequesterId, false); IncomingRoomConfigErrors.Enqueue(MoveTemp(OutError)); } return true; } bool FXmppMultiUserChatStrophe::CreateRoom(const FXmppRoomId& RoomId, const FString& Nickname, const FXmppRoomConfig& RoomConfig) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: CreateRoom=%s Nickname=%s"), *RoomId, *Nickname); bool bSuccess = false; FString ErrorStr; if (RoomId.IsEmpty()) { ErrorStr = TEXT("Room ID Invalid"); } else if (Nickname.IsEmpty()) { ErrorStr = TEXT("Nickname is Invalid"); } else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { ErrorStr = TEXT("Not currently connected"); } else { FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId); // Set the Room's ID if we just created it if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid()) { Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname); Room.Info.Id = Room.RoomJid.Id; } if (Room.Status == ERoomStatusStrophe::Joined) { ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId); } else if (Room.Status != ERoomStatusStrophe::NotJoined) { ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId); } else { Room.Status = ERoomStatusStrophe::CreatePending; // cache off the config for use after the room is created & ready to be configured PendingRoomCreateConfigs.Emplace(RoomId, RoomConfig); bSuccess = SendJoinRoomStanza(Room); } } if (!bSuccess) { UE_LOG(LogXmpp, Warning, TEXT("MUC: CreateRoom failed. %s"), *ErrorStr); OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr); } return bSuccess; } bool FXmppMultiUserChatStrophe::ConfigureRoom(const FXmppRoomId& RoomId, const FXmppRoomConfig& RoomConfig) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: ConfigureRoom RoomId=%s"), *RoomId); bool bSuccess = false; FString ErrorStr; if (RoomId.IsEmpty()) { ErrorStr = TEXT("Room ID Invalid"); } FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr == nullptr) { ErrorStr = FString::Printf(TEXT("Could not find room %s"), *RoomId); } else if (RoomPtr->Status != ERoomStatusStrophe::Joined) { ErrorStr = FString::Printf(TEXT("You must be in room %s to configure it."), *RoomId); } else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { ErrorStr = TEXT("You are not currently connected to the server"); } else if (RoomPtr->Info.OwnerId != RoomPtr->GetNickname()) { ErrorStr = FString::Printf(TEXT("You must be the owner of room %s to configure it. The current owner is %s"), *RoomId, *RoomPtr->Info.OwnerId); } else { bSuccess = InternalConfigureRoom(*RoomPtr, RoomConfig, EConfigureRoomTypeStrophe::UseConfigCallback); if (!bSuccess) { ErrorStr = TEXT("Failed to configure room"); } } if (!bSuccess) { UE_LOG(LogXmpp, Warning, TEXT("MUC: Failed to configure Room=%s Error=%s"), *RoomId, *ErrorStr); OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr); } return bSuccess; } bool FXmppMultiUserChatStrophe::RefreshRoomInfo(const FXmppRoomId& RoomId) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: RefreshRoomInfo RoomId=%s"), *RoomId); // This just prints a bunch of info to the console in our libjingle module, so we're going to // skip writing a bunch of code that doesn't do anything and just call our delegate instead if (Chatrooms.Find(RoomId) != nullptr) { OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomId, FString()); return true; } else { OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), false, RoomId, TEXT("Room does not exist")); return false; } } bool FXmppMultiUserChatStrophe::JoinPublicRoom(const FXmppRoomId& RoomId, const FString& Nickname) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: JoinPublicRoom RoomId=%s Nickname=%s"), *RoomId, *Nickname); bool bSuccess = false; FString ErrorStr; if (RoomId.IsEmpty()) { ErrorStr = TEXT("Room ID Invalid"); } else if (Nickname.IsEmpty()) { ErrorStr = TEXT("Nickname is Invalid"); } else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { ErrorStr = TEXT("Not currently connected"); } else { FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId); // Set the Room's ID if we just created it if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid()) { Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname); Room.Info.Id = Room.RoomJid.Id; } if (Room.Status == ERoomStatusStrophe::Joined) { ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId); } else if (Room.Status != ERoomStatusStrophe::NotJoined) { ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId); } else { Room.Status = ERoomStatusStrophe::JoinPublicPending; bSuccess = SendJoinRoomStanza(Room); } } if (!bSuccess) { UE_LOG(LogXmpp, Warning, TEXT("MUC: JoinPublicRoom failed. %s"), *ErrorStr); OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr); } return bSuccess; } bool FXmppMultiUserChatStrophe::JoinPrivateRoom(const FXmppRoomId& RoomId, const FString& Nickname, const FString& Password) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: JoinPrivateRoom RoomId=%s Nickname=%s"), *RoomId, *Nickname); bool bSuccess = false; FString ErrorStr; if (RoomId.IsEmpty()) { ErrorStr = TEXT("Room ID Invalid"); } else if (Nickname.IsEmpty()) { ErrorStr = TEXT("Nickname is Invalid"); } else if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { ErrorStr = TEXT("Not currently connected"); } else { FXmppRoomStrophe& Room = Chatrooms.FindOrAdd(RoomId); // Set the Room's ID if we just created it if (Room.Info.Id.IsEmpty() || !Room.RoomJid.IsValid()) { Room.RoomJid = FXmppUserJid(RoomId, ConnectionManager.GetMucDomain(), Nickname); Room.Info.Id = Room.RoomJid.Id; } if (Room.Status == ERoomStatusStrophe::Joined) { ErrorStr = FString::Printf(TEXT("Already in room %s"), *RoomId); } else if (Room.Status != ERoomStatusStrophe::NotJoined) { ErrorStr = FString::Printf(TEXT("Another operation already pending for room %s"), *RoomId); } else { Room.Status = ERoomStatusStrophe::JoinPrivatePending; bSuccess = SendJoinRoomStanza(Room, Password); } } if (!bSuccess) { UE_LOG(LogXmpp, Warning, TEXT("MUC: JoinPrivateRoom failed. %s"), *ErrorStr); // trigger delegates on error OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr); } return bSuccess; } bool FXmppMultiUserChatStrophe::RegisterMember(const FXmppRoomId& RoomId, const FString& Nickname) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: RegisterMember RoomId=%s Nickname=%s"), *RoomId, *Nickname); // No-op currently in libjingle version return false; } bool FXmppMultiUserChatStrophe::UnregisterMember(const FXmppRoomId& RoomId, const FString& Nickname) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: UnregisterMember RoomId=%s Nickname=%s"), *RoomId, *Nickname); // No-op currently in libjingle version return false; } bool FXmppMultiUserChatStrophe::ExitRoom(const FXmppRoomId& RoomId) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: ExitRoom RoomId=%s"), *RoomId); bool bSuccess = false; FString ErrorStr; FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { // Not logged in, so cannot leave the chat room ErrorStr = TEXT("User not logged in"); bSuccess = false; } else if (RoomPtr == nullptr) { ErrorStr = TEXT("Room was never created."); bSuccess = true; // Returning true since we can't be in the room at all. } else { switch (RoomPtr->Status) { case ERoomStatusStrophe::ExitPending: { // We've already queued an exit, so do not queue again or trigger delegates bSuccess = true; } break; case ERoomStatusStrophe::Joined: { ERoomStatusStrophe OldStatus = RoomPtr->Status; // Queue our exit RoomPtr->Status = ERoomStatusStrophe::ExitPending; bSuccess = SendExitRoomStanza(*RoomPtr); if (!bSuccess) { ErrorStr = TEXT("Could not send ExitRoom stanza"); RoomPtr->Status = OldStatus; // Restore old room status if we could not queue the exit operation } } break; case ERoomStatusStrophe::NotJoined: case ERoomStatusStrophe::CreatePending: case ERoomStatusStrophe::JoinPublicPending: case ERoomStatusStrophe::JoinPrivatePending: default: { // We're (probably) not in this room, so we don't need to exit ErrorStr = FString::Printf(TEXT("User not in room: status is %s"), LexToString(RoomPtr->Status)); bSuccess = false; } break; } } if (!bSuccess || RoomPtr == nullptr) { UE_LOG(LogXmpp, Warning, TEXT("MUC: Cannot leave room %s: %s"), *RoomId, *ErrorStr); OnXmppRoomExitCompleteDelegate.Broadcast(ConnectionManager.AsShared(), bSuccess, RoomId, ErrorStr); } return bSuccess; } bool FXmppMultiUserChatStrophe::SendChat(const FXmppRoomId& RoomId, const FString& MsgBody, const FString& ChatInfo) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: SendChat RoomId=%s"), *RoomId); if (ConnectionManager.GetLoginStatus() != EXmppLoginStatus::LoggedIn) { return false; } FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr == nullptr) { return false; } FStropheStanza MessageStanza(ConnectionManager, Strophe::SN_MESSAGE); { MessageStanza.SetId(FGuid::NewGuid().ToString()); MessageStanza.SetType(Strophe::ST_GROUPCHAT); MessageStanza.SetTo(RoomPtr->GetRoomJid().GetBareId()); MessageStanza.AddBodyWithText(MsgBody); } return ConnectionManager.SendStanza(MoveTemp(MessageStanza)); } void FXmppMultiUserChatStrophe::GetJoinedRooms(TArray& OutRooms) { OutRooms.Empty(Chatrooms.Num()); for (const TMap::ElementType& Pair : Chatrooms) { OutRooms.Emplace(Pair.Key); } } bool FXmppMultiUserChatStrophe::GetRoomInfo(const FXmppRoomId& RoomId, FXmppRoomInfo& OutRoomInfo) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { OutRoomInfo = RoomPtr->Info; return true; } return false; } bool FXmppMultiUserChatStrophe::GetMembers(const FXmppRoomId& RoomId, TArray& OutMembers) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { OutMembers = RoomPtr->Members; return true; } return false; } FXmppChatMemberPtr FXmppMultiUserChatStrophe::GetMember(const FXmppRoomId& RoomId, const FXmppUserJid& MemberJid) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { FXmppChatMemberPtr FoundMember = RoomPtr->GetMember(MemberJid); return FoundMember; } return nullptr; } bool FXmppMultiUserChatStrophe::GetLastMessages(const FXmppRoomId& RoomId, int32 NumMessages, TArray< TSharedRef >& OutMessages) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { const int32 MessagesToFetch = FMath::Max(FMath::Min(NumMessages, RoomPtr->LastMessages.Num()), 0); OutMessages.Empty(MessagesToFetch); for (int32 Index = 0; Index < MessagesToFetch; ++Index) { check(RoomPtr->LastMessages.IsValidIndex(Index)); OutMessages.Emplace(RoomPtr->LastMessages[Index]); } return true; } OutMessages.Empty(); return false; } void FXmppMultiUserChatStrophe::HandleMucPresence(const FXmppMucPresence& MemberPresence) { // We don't use this, but it's built into the interface for reasons? } void FXmppMultiUserChatStrophe::DumpMultiUserChatState() const { LOG_SCOPE_VERBOSITY_OVERRIDE(LogXmpp, ELogVerbosity::Display); for (const TMap::ElementType& Room : Chatrooms) { const FXmppRoomId& RoomId = Room.Key; const FXmppRoomStrophe& XmppRoom = Room.Value; UE_LOG(LogXmpp, Display, TEXT("RoomId: %s"), *RoomId); UE_LOG(LogXmpp, Display, TEXT(" Owner: %s Subj: %s Priv: %d"), *XmppRoom.Info.OwnerId, *XmppRoom.Info.Subject, XmppRoom.Info.bIsPrivate); UE_LOG(LogXmpp, Display, TEXT(" Status: %d"), (int32)XmppRoom.Status); UE_LOG(LogXmpp, Display, TEXT(" Members: %d"), XmppRoom.Members.Num()); for (const FXmppChatMemberRef& Member : XmppRoom.Members) { UE_LOG(LogXmpp, Display, TEXT(" %s"), *Member->ToDebugString()); } } } bool FXmppMultiUserChatStrophe::Tick(float DeltaTime) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FXmppMultiUserChatStrophe_Tick); while (!IncomingMucPresenceUpdates.IsEmpty()) { TUniquePtr MucPresence; if (IncomingMucPresenceUpdates.Dequeue(MucPresence)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); check(MucPresence.IsValid()); OnReceiveMucPresence(MoveTemp(*MucPresence)); } } while (!IncomingMucPresenceErrors.IsEmpty()) { FXmppStropheErrorPair ErrorInfo; if (IncomingMucPresenceErrors.Dequeue(ErrorInfo)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceiveMucPresenceError(MoveTemp(ErrorInfo)); } } while (!IncomingGroupChatMessages.IsEmpty()) { TUniquePtr GroupChatMessage; if (IncomingGroupChatMessages.Dequeue(GroupChatMessage)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceiveGroupChatMessage(MoveTemp(GroupChatMessage)); } } while (!IncomingRoomSubjects.IsEmpty()) { FXmppStropheSubjectUpdate NewSubject; if (IncomingRoomSubjects.Dequeue(NewSubject)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceiveGroupChatSubject(MoveTemp(NewSubject)); } } while (!IncomingRoomConfigErrors.IsEmpty()) { FXmppStropheErrorPair RoomConfigError; if (IncomingRoomConfigErrors.Dequeue(RoomConfigError)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceiveRoomConfigError(MoveTemp(RoomConfigError)); } } while (!IncomingRoomConfigWriteSuccesses.IsEmpty()) { FXmppRoomId RoomId; if (IncomingRoomConfigWriteSuccesses.Dequeue(RoomId)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceiveRoomConfigSuccess(MoveTemp(RoomId)); } } while (!IncomingRoomInfoUpdates.IsEmpty()) { FXmppRoomId RoomId; if (IncomingRoomInfoUpdates.Dequeue(RoomId)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); OnReceieveRoomInfoUpdate(MoveTemp(RoomId)); } } return true; } void FXmppMultiUserChatStrophe::OnReceiveMucPresence(FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, VeryVerbose, TEXT("MUC: OnReceiveMucPresence: jid=%s nick=%s roomid=%s role=%s affiliation=%s"), *MemberPresence.UserJid.ToDebugString(), *MemberPresence.GetNickName(), *MemberPresence.GetRoomId(), *MemberPresence.Role, *MemberPresence.Affiliation); FXmppRoomStrophe* RoomPtr = Chatrooms.Find(MemberPresence.GetRoomId()); if (RoomPtr != nullptr) { const bool bUpdateIsUs = MemberPresence.GetNickName().Contains(ConnectionManager.GetUserJid().Id, ESearchCase::CaseSensitive); const bool bLeftRoom = !MemberPresence.bIsAvailable; const bool bCreateRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::CreatePending; const bool bJoinPublicRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::JoinPublicPending; const bool bJoinPrivateRoom = bUpdateIsUs && !bLeftRoom && RoomPtr->Status == ERoomStatusStrophe::JoinPrivatePending; const bool bAlreadyInRoom = RoomPtr->HasMember(MemberPresence.UserJid); if (bUpdateIsUs) { // This presence update is us doing something if (bLeftRoom) { HandleExitRoomComplete(*RoomPtr, MoveTemp(MemberPresence)); } else if (bCreateRoom) { HandleCreateRoomComplete(*RoomPtr, MoveTemp(MemberPresence)); } else if (bJoinPrivateRoom) { HandleJoinPrivateRoomComplete(*RoomPtr, MoveTemp(MemberPresence)); } else if (bJoinPublicRoom) { HandleJoinPublicRoomComplete(*RoomPtr, MoveTemp(MemberPresence)); } else { ensureMsgf(false, TEXT("Unknown libstrope Presence Self-State update")); } } // Other users else { if (!bAlreadyInRoom && !bLeftRoom) { // Anyone we didn't know about, that isn't leaving, is joining HandleRoomMemberJoined(*RoomPtr, MoveTemp(MemberPresence)); } else if (bLeftRoom) { HandleRoomMemberLeft(*RoomPtr, MoveTemp(MemberPresence)); } else { HandleRoomMemberChanged(*RoomPtr, MoveTemp(MemberPresence)); } } } else { UE_LOG(LogXmpp, VeryVerbose, TEXT("MUC: OnReceiveMucPresence Ignored presence from room we haven't joined: Room=%s Connjid=%s"), *MemberPresence.GetRoomId(), *ConnectionManager.GetUserJid().Id); } } void FXmppMultiUserChatStrophe::OnReceiveMucPresenceError(FXmppStropheErrorPair&& PresenceError) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(PresenceError.RoomId); if (RoomPtr != nullptr) { switch (RoomPtr->Status) { case ERoomStatusStrophe::CreatePending: Chatrooms.Remove(PresenceError.RoomId); OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage); break; case ERoomStatusStrophe::JoinPrivatePending: Chatrooms.Remove(PresenceError.RoomId); OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage); break; case ERoomStatusStrophe::JoinPublicPending: Chatrooms.Remove(PresenceError.RoomId); OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, PresenceError.RoomId, PresenceError.ErrorMessage); break; case ERoomStatusStrophe::ExitPending: case ERoomStatusStrophe::NotJoined: case ERoomStatusStrophe::Joined: // No-Op? break; } } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveMucPresenceError Received Error from room we haven't joined: Room=%s Connjid=%s Error=%s"), *PresenceError.RoomId, *ConnectionManager.GetUserJid().Id, *PresenceError.ErrorMessage); } } void FXmppMultiUserChatStrophe::OnReceiveGroupChatMessage(TUniquePtr&& GroupChatMessage) { check(GroupChatMessage.IsValid()); FXmppRoomStrophe* RoomPtr = Chatrooms.Find(GroupChatMessage->FromJid.Id); if (RoomPtr != nullptr) { TSharedRef SharedChatMessageRef = MakeShareable(GroupChatMessage.Release()); RoomPtr->AddNewMessage(SharedChatMessageRef); OnXmppRoomChatReceivedDelegate.Broadcast(ConnectionManager.AsShared(), SharedChatMessageRef->FromJid.Id, SharedChatMessageRef->FromJid, SharedChatMessageRef); } else { UE_LOG(LogXmpp, Log, TEXT("MUC: OnReceiveGroupChatMessage Ignored GroupChat from room we haven't joined: Room=%s Connjid=%s"), *GroupChatMessage->FromJid.Id, *ConnectionManager.GetUserJid().Id); } } void FXmppMultiUserChatStrophe::OnReceiveGroupChatSubject(FXmppStropheSubjectUpdate&& SubjectUpdate) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(SubjectUpdate.RoomId); if (RoomPtr != nullptr) { RoomPtr->Info.Subject = MoveTemp(SubjectUpdate.NewSubject); OnXmppRoomInfoRefreshedDelegate.Broadcast(ConnectionManager.AsShared(), true, SubjectUpdate.RoomId, FString()); } else { UE_LOG(LogXmpp, Log, TEXT("MUC: OnReceiveGroupChatMessage Ignored Updated Room Subject from room we haven't joined: Room=%s Connjid=%s NewSubject=%s"), *SubjectUpdate.RoomId, *ConnectionManager.GetUserJid().Id, *SubjectUpdate.NewSubject); }} void FXmppMultiUserChatStrophe::OnReceiveRoomConfigError(FXmppStropheErrorPair&& RoomConfigError) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomConfigError.RoomId); if (RoomPtr != nullptr) { EConfigureRoomTypeStrophe* CallbackTypePtr = PendingRoomConfigCallbacks.Find(RoomPtr->GetRoomId()); if (ensure(CallbackTypePtr != nullptr)) { switch (*CallbackTypePtr) { case EConfigureRoomTypeStrophe::NoCallback: // No-Op return; // Return on purpose so we don't log below case EConfigureRoomTypeStrophe::UseConfigCallback: OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), false, RoomPtr->GetRoomId(), RoomConfigError.ErrorMessage); break; case EConfigureRoomTypeStrophe::UseCreateCallback: SendExitRoomStanza(*RoomPtr); break; } UE_LOG(LogXmpp, Warning, TEXT("MUC: Failed to Configure Room Room=%s Error=%s"), *RoomConfigError.RoomId, *RoomConfigError.ErrorMessage); } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigError Received Error from room we have no callback for: Room=%s Connjid=%s Error=%s"), *RoomConfigError.RoomId, *ConnectionManager.GetUserJid().Id, *RoomConfigError.ErrorMessage); } } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigError Received Error from room we haven't joined: Room=%s Connjid=%s Error=%s"), *RoomConfigError.RoomId, *ConnectionManager.GetUserJid().Id, *RoomConfigError.ErrorMessage); } } void FXmppMultiUserChatStrophe::OnReceiveRoomConfigSuccess(FXmppRoomId&& RoomId) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { EConfigureRoomTypeStrophe* CallbackTypePtr = PendingRoomConfigCallbacks.Find(RoomPtr->GetRoomId()); if (ensure(CallbackTypePtr != nullptr)) { switch (*CallbackTypePtr) { case EConfigureRoomTypeStrophe::NoCallback: // No-Op break; case EConfigureRoomTypeStrophe::UseConfigCallback: OnXmppRoomConfiguredDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomPtr->GetRoomId(), FString()); break; case EConfigureRoomTypeStrophe::UseCreateCallback: if (ensure(RoomPtr->Status == ERoomStatusStrophe::CreatePending)) { RoomPtr->Status = ERoomStatusStrophe::Joined; } else { UE_LOG(LogXmpp, Warning, TEXT("OnReceiveRoomConfigSuccess: Room %s is not in the CreatePending state (%s)"), *RoomPtr->GetRoomId(), LexToString(RoomPtr->Status)); } OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, RoomPtr->GetRoomId(), FString()); break; } UE_LOG(LogXmpp, Verbose, TEXT("MUC: OnReceiveRoomConfigSuccess Received success for room %s"), *RoomId); } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigSuccess Received success from room with no callback Room=%s"), *RoomId); } } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceiveRoomConfigSuccess Received RoomConfig from room we haven't joined: Room=%s Connjid=%s"), *RoomId, *ConnectionManager.GetUserJid().Id); } } void FXmppMultiUserChatStrophe::OnReceieveRoomInfoUpdate(FXmppRoomId&& RoomId) { FXmppRoomStrophe* RoomPtr = Chatrooms.Find(RoomId); if (RoomPtr != nullptr) { SendRequestRoomInfoConfigStanza(*RoomPtr); } else { UE_LOG(LogXmpp, Warning, TEXT("MUC: OnReceieveRoomInfoUpdate Received RoomInfoUpdate for room we haven't joined: Room=%s Connjid=%s"), *RoomId, *ConnectionManager.GetUserJid().Id); } } bool FXmppMultiUserChatStrophe::SendJoinRoomStanza(const FXmppRoomStrophe& Room, const FString& Password) { // Create/Join room FStropheStanza JoinRoomStanza(ConnectionManager, Strophe::SN_PRESENCE); JoinRoomStanza.SetTo(Room.GetRoomJid()); { FStropheStanza XStanza(ConnectionManager, Strophe::SN_X); XStanza.SetNamespace(Strophe::SNS_MUC); if (!Password.IsEmpty()) { FStropheStanza PasswordStanza(ConnectionManager, Strophe::SN_PASSWORD); PasswordStanza.SetText(Password); XStanza.AddChild(PasswordStanza); } { FStropheStanza HistoryStanza(ConnectionManager, Strophe::SN_HISTORY); HistoryStanza.SetAttribute(Strophe::SA_MAXSTANZAS, FString::FromInt(MAX_MESSAGE_HISTORY)); XStanza.AddChild(HistoryStanza); } JoinRoomStanza.AddChild(XStanza); } return ConnectionManager.SendStanza(MoveTemp(JoinRoomStanza)); } bool FXmppMultiUserChatStrophe::SendExitRoomStanza(const FXmppRoomStrophe& Room) { FStropheStanza ExitPresence(ConnectionManager, Strophe::SN_PRESENCE); { ExitPresence.SetTo(Room.GetRoomJid()); ExitPresence.SetType(Strophe::ST_UNAVAILABLE); } return ConnectionManager.SendStanza(MoveTemp(ExitPresence)); } bool FXmppMultiUserChatStrophe::SendRequestRoomInfoConfigStanza(const FXmppRoomStrophe& Room) { FStropheStanza IQStanza(ConnectionManager, Strophe::SN_IQ); { IQStanza.SetId(FGuid::NewGuid().ToString()); IQStanza.SetTo(Room.GetRoomJid().GetBareId()); IQStanza.SetFrom(ConnectionManager.GetUserJid()); IQStanza.SetType(Strophe::ST_GET); { FStropheStanza QueryStanza(ConnectionManager, Strophe::SN_QUERY); QueryStanza.SetNamespace(Strophe::SNS_DISCO_INFO); IQStanza.AddChild(QueryStanza); } } return ConnectionManager.SendStanza(MoveTemp(IQStanza)); } bool FXmppMultiUserChatStrophe::InternalConfigureRoom(const FXmppRoomStrophe& Room, const FXmppRoomConfig& RoomConfig, EConfigureRoomTypeStrophe CallbackType) { const auto SetStanzaConfig = [this](FStropheStanza& ParentStanza, const FString& Key, const FString& Value) { FStropheStanza FieldStanza(ConnectionManager, Strophe::SN_FIELD); FieldStanza.SetAttribute(Strophe::SA_VAR, Key); { FStropheStanza ValueStanza(ConnectionManager, Strophe::SN_VALUE); ValueStanza.SetText(Value); FieldStanza.AddChild(ValueStanza); } ParentStanza.AddChild(FieldStanza); }; FStropheStanza IQStanza(ConnectionManager, Strophe::SN_IQ); { IQStanza.SetId(FGuid::NewGuid().ToString()); IQStanza.SetTo(Room.GetRoomJid().GetBareId()); IQStanza.SetFrom(ConnectionManager.GetUserJid()); IQStanza.SetType(Strophe::ST_SET); FStropheStanza QueryStanza(ConnectionManager, Strophe::SN_QUERY); { QueryStanza.SetNamespace(Strophe::SNS_MUC_OWNER); FStropheStanza XStanza(ConnectionManager, Strophe::SN_X); { XStanza.SetNamespace(Strophe::SNS_X_DATA); XStanza.SetType(Strophe::ST_SUBMIT); SetStanzaConfig(XStanza, TEXT("FORM_TYPE"), TEXT("http://jabber.org/protocol/muc#roomconfig")); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomname"), RoomConfig.RoomName); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomdesc"), RoomConfig.RoomDesc); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_persistentroom"), RoomConfig.bIsPersistent ? TEXT("1") : TEXT("0")); SetStanzaConfig(XStanza, TEXT("muc#maxhistoryfetch"), FString::FromInt(RoomConfig.MaxMsgHistory)); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_changesubject"), RoomConfig.bAllowChangeSubject ? TEXT("1") : TEXT("0")); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_anonymity"), FXmppRoomConfig::ConvertRoomAnonymityToString(RoomConfig.RoomAnonymity)); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_membersonly"), RoomConfig.bIsMembersOnly ? TEXT("1") : TEXT("0")); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_moderatedroom"), RoomConfig.bIsModerated ? TEXT("1") : TEXT("0")); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_publicroom"), RoomConfig.bAllowPublicSearch ? TEXT("1") : TEXT("0")); SetStanzaConfig(XStanza, TEXT("muc#roomconfig_passwordprotectedroom"), RoomConfig.bIsPrivate ? TEXT("1") : TEXT("0")); if (RoomConfig.bIsPrivate) { SetStanzaConfig(XStanza, TEXT("muc#roomconfig_roomsecret"), RoomConfig.Password); } QueryStanza.AddChild(XStanza); } } IQStanza.AddChild(QueryStanza); } PendingRoomConfigCallbacks.Emplace(Room.GetRoomId(), CallbackType); return ConnectionManager.SendStanza(MoveTemp(IQStanza)); } void FXmppMultiUserChatStrophe::HandleCreateRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleCreateRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); FXmppChatMemberRef ChatMember = MakeShareable(new FXmppChatMember(MemberPresence)); Room.Members.Emplace(ChatMember); FXmppRoomConfig* RoomConfigPtr = PendingRoomCreateConfigs.Find(Room.GetRoomId()); if (ensure(RoomConfigPtr != nullptr)) { const bool bIsOwner = ChatMember->Affiliation == EXmppChatMemberAffiliation::Owner; if (bIsOwner) { const bool bSuccess = InternalConfigureRoom(Room, *RoomConfigPtr, EConfigureRoomTypeStrophe::UseCreateCallback); if (!bSuccess) { SendExitRoomStanza(Room); } } else { Room.Status = ERoomStatusStrophe::Joined; // We joined instead of creating OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString()); } // Remove our pending configurations after we've copied things out in ConfigureRoom PendingRoomCreateConfigs.Remove(Room.GetRoomId()); } else { Room.Status = ERoomStatusStrophe::Joined; OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, Room.GetRoomId(), TEXT("Missing Room Configuration")); } } void FXmppMultiUserChatStrophe::HandleJoinPrivateRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleJoinPrivateRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); Room.Status = ERoomStatusStrophe::Joined; Room.Members.Emplace(MakeShareable(new FXmppChatMember(MemberPresence))); OnXmppRoomJoinPrivateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString()); } void FXmppMultiUserChatStrophe::HandleJoinPublicRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleJoinPublicRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); Room.Status = ERoomStatusStrophe::Joined; Room.Members.Emplace(MakeShareable(new FXmppChatMember(MemberPresence))); OnXmppRoomJoinPublicCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString()); } void FXmppMultiUserChatStrophe::HandleExitRoomComplete(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleExitRoomComplete: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); if (Room.Status == ERoomStatusStrophe::CreatePending) { OnXmppRoomCreateCompleteDelegate.Broadcast(ConnectionManager.AsShared(), false, Room.GetRoomId(), TEXT("Failed to configure room")); } else { if (Room.Status != ERoomStatusStrophe::ExitPending) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: Server initiated room exit complete; in state %s"), LexToString(Room.Status)); } OnXmppRoomExitCompleteDelegate.Broadcast(ConnectionManager.AsShared(), true, Room.GetRoomId(), FString()); } // Do not use Room after this Chatrooms.Remove(Room.GetRoomId()); } void FXmppMultiUserChatStrophe::HandleRoomMemberJoined(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberJoined: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); FXmppChatMemberRef NewMember = MakeShareable(new FXmppChatMember(MemberPresence)); if (NewMember->Affiliation == EXmppChatMemberAffiliation::Owner) { Room.Info.OwnerId = MemberPresence.UserJid.Resource; } Room.Members.Emplace(NewMember); OnXmppRoomMemberJoinDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid); } void FXmppMultiUserChatStrophe::HandleRoomMemberChanged(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberChanged: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); for (FXmppChatMemberRef& Member : Room.Members) { if (Member->RoomMemberJid == MemberPresence.UserJid) { // We don't need to update Nickname as it's part of the User's JID Member->UserPresence = MemberPresence; Member->Affiliation = EXmppChatMemberAffiliation::ToType(MemberPresence.Affiliation); Member->Role = EXmppChatMemberRole::ToType(MemberPresence.Role); break; } } OnXmppRoomMemberChangedDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid); } void FXmppMultiUserChatStrophe::HandleRoomMemberLeft(FXmppRoomStrophe& Room, FXmppMucPresence&& MemberPresence) { UE_LOG(LogXmpp, Verbose, TEXT("MUC: HandleRoomMemberLeft: Room: %s User: %s"), *Room.GetRoomId(), *MemberPresence.GetNickName()); const int32 RoomMemberCount = Room.Members.Num(); for (int32 Index = 0; Index < RoomMemberCount; ++Index) { const FXmppChatMemberRef& Member = Room.Members[Index]; if (Member->RoomMemberJid == MemberPresence.UserJid) { Room.Members.RemoveAt(Index); break; } } OnXmppRoomMemberExitDelegate.Broadcast(ConnectionManager.AsShared(), Room.GetRoomId(), MemberPresence.UserJid); } void FXmppMultiUserChatStrophe::CleanupMessages() { while (!IncomingMucPresenceUpdates.IsEmpty()) { TUniquePtr MucPresence; IncomingMucPresenceUpdates.Dequeue(MucPresence); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingMucPresenceErrors.IsEmpty()) { FXmppStropheErrorPair ErrorInfo; IncomingMucPresenceErrors.Dequeue(ErrorInfo); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingGroupChatMessages.IsEmpty()) { TUniquePtr GroupChatMessage; IncomingGroupChatMessages.Dequeue(GroupChatMessage); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingRoomSubjects.IsEmpty()) { FXmppStropheSubjectUpdate NewSubject; IncomingRoomSubjects.Dequeue(NewSubject); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingRoomConfigErrors.IsEmpty()) { FXmppStropheErrorPair RoomConfigError; IncomingRoomConfigErrors.Dequeue(RoomConfigError); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingRoomConfigWriteSuccesses.IsEmpty()) { FXmppRoomId RoomId; IncomingRoomConfigWriteSuccesses.Dequeue(RoomId); FEmbeddedCommunication::AllowSleep(TickRequesterId); } while (!IncomingRoomInfoUpdates.IsEmpty()) { FXmppRoomId RoomId; IncomingRoomInfoUpdates.Dequeue(RoomId); FEmbeddedCommunication::AllowSleep(TickRequesterId); } } #undef TickRequesterId #endif