// Copyright Epic Games, Inc. All Rights Reserved. #include "XmppStrophe/XmppPresenceStrophe.h" #include "XmppStrophe/XmppConnectionStrophe.h" #include "XmppStrophe/StropheStanza.h" #include "XmppStrophe/StropheStanzaConstants.h" #include "Misc/EmbeddedCommunication.h" #include "Containers/BackgroundableTicker.h" #include "Stats/Stats.h" #if WITH_XMPP_STROPHE #define TickRequesterId FName("StrophePresence") FXmppPresenceStrophe::FXmppPresenceStrophe(FXmppConnectionStrophe& InConnectionManager) : FTSTickerObjectBase(0.0f, FTSBackgroundableTicker::GetCoreTicker()) , ConnectionManager(InConnectionManager) { } FXmppPresenceStrophe::~FXmppPresenceStrophe() { CleanupMessages(); } void FXmppPresenceStrophe::OnDisconnect() { if (RosterMembers.Num() > 0) { RosterMembers.Empty(); } CleanupMessages(); } void FXmppPresenceStrophe::OnReconnect() { // Triggered by login request when already connected // re-broadcast all cached presence entries for (const auto& Pair : RosterMembers) { const TSharedRef& Presence = Pair.Value; OnXmppPresenceReceivedDelegate.Broadcast(ConnectionManager.AsShared(), Presence->UserJid, Presence); } } bool FXmppPresenceStrophe::ReceiveStanza(const FStropheStanza& IncomingStanza) { if (IncomingStanza.GetName() != Strophe::SN_PRESENCE) { return false; } FXmppUserJid FromJid = IncomingStanza.GetFrom(); bool bIsMucPresence = FromJid.Domain.Equals(ConnectionManager.GetMucDomain(), ESearchCase::CaseSensitive); if (bIsMucPresence) { // Our MultiUserChat interface will handle this stanza return false; } else if (FromJid.Resource.IsEmpty()) { // Skip user presence updates that are missing a resource return true; } // We build this into a MucPresence and slice it down to a UserPresence if it's a user presence FXmppMucPresence Presence; Presence.UserJid = MoveTemp(FromJid); if (IncomingStanza.GetType() == Strophe::ST_UNAVAILABLE) { Presence.bIsAvailable = false; } else { Presence.bIsAvailable = true; TOptional StatusTextStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_STATUS); if (StatusTextStanza.IsSet()) { Presence.StatusStr = StatusTextStanza->GetText(); } Presence.Status = EXmppPresenceStatus::Online; TOptional StatusEnumStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_SHOW); if (StatusEnumStanza.IsSet()) { FString StatusEnum(StatusEnumStanza->GetText()); if (StatusEnum == TEXT("away")) { Presence.Status = EXmppPresenceStatus::Away; } else if (StatusEnum == TEXT("chat")) { Presence.Status = EXmppPresenceStatus::Chat; } else if (StatusEnum == TEXT("dnd")) { Presence.Status = EXmppPresenceStatus::DoNotDisturb; } else if (StatusEnum == TEXT("xa")) { Presence.Status = EXmppPresenceStatus::ExtendedAway; } } TOptional TimestampStanza = IncomingStanza.GetChildStropheStanza(Strophe::SN_DELAY); if (TimestampStanza.IsSet()) { FDateTime::ParseIso8601(*TimestampStanza->GetAttribute(Strophe::SA_STAMP), Presence.SentTime); } Presence.ReceivedTime = FDateTime::UtcNow(); FString UnusedPlatformUserId; Presence.UserJid.ParseResource(Presence.AppId, Presence.Platform, UnusedPlatformUserId); } FEmbeddedCommunication::KeepAwake(TickRequesterId, false); return IncomingPresenceUpdates.Enqueue(MakeUnique(MoveTemp(Presence))); } bool FXmppPresenceStrophe::UpdatePresence(const FXmppUserPresence& NewPresence) { FStropheStanza PresenceStanza(ConnectionManager, Strophe::SN_PRESENCE); if (NewPresence.bIsAvailable) { // Update Availability { FStropheStanza AvailabilityStanza(ConnectionManager, Strophe::SN_SHOW); switch (NewPresence.Status) { case EXmppPresenceStatus::Away: AvailabilityStanza.SetText(TEXT("away")); PresenceStanza.AddChild(AvailabilityStanza); break; case EXmppPresenceStatus::Chat: AvailabilityStanza.SetText(TEXT("chat")); PresenceStanza.AddChild(AvailabilityStanza); break; case EXmppPresenceStatus::DoNotDisturb: AvailabilityStanza.SetText(TEXT("dnd")); PresenceStanza.AddChild(AvailabilityStanza); break; case EXmppPresenceStatus::ExtendedAway: AvailabilityStanza.SetText(TEXT("xa")); PresenceStanza.AddChild(AvailabilityStanza); break; } } // Update Status String if (!NewPresence.StatusStr.IsEmpty()) { FStropheStanza StatusStanza(ConnectionManager, Strophe::SN_STATUS); StatusStanza.SetText(NewPresence.StatusStr); PresenceStanza.AddChild(StatusStanza); } // Update Sent Time { FStropheStanza DelayStanza(ConnectionManager, Strophe::SN_DELAY); DelayStanza.SetNamespace(Strophe::SNS_DELAY); DelayStanza.SetAttribute(Strophe::SA_STAMP, FDateTime::UtcNow().ToIso8601()); PresenceStanza.AddChild(DelayStanza); } } else { PresenceStanza.SetType(Strophe::ST_UNAVAILABLE); } const bool bSuccess = ConnectionManager.SendStanza(MoveTemp(PresenceStanza)); if (bSuccess) { CachedPresence = NewPresence; } return bSuccess; } const FXmppUserPresence& FXmppPresenceStrophe::GetPresence() const { return CachedPresence; } bool FXmppPresenceStrophe::QueryPresence(const FString& UserId) { // Not supported by tigase return false; } TArray> FXmppPresenceStrophe::GetRosterPresence(const FString& UserId) { TArray> OutRoster; for (const TMap>::ElementType& Pair : RosterMembers) { if (Pair.Value->UserJid.Id == UserId) { OutRoster.Emplace(Pair.Value); } } return OutRoster; } void FXmppPresenceStrophe::GetRosterMembers(TArray& Members) { check(IsInGameThread()); Members.Empty(RosterMembers.Num()); for (const TMap>::ElementType& Pair : RosterMembers) { Members.Emplace(Pair.Value->UserJid); } } bool FXmppPresenceStrophe::Tick(float DeltaTime) { QUICK_SCOPE_CYCLE_COUNTER(STAT_FXmppPresenceStrophe_Tick); while (!IncomingPresenceUpdates.IsEmpty()) { TUniquePtr PresencePtr; if (IncomingPresenceUpdates.Dequeue(PresencePtr)) { FEmbeddedCommunication::AllowSleep(TickRequesterId); check(PresencePtr.IsValid()); OnPresenceUpdate(MoveTemp(PresencePtr)); } } return true; } void FXmppPresenceStrophe::OnPresenceUpdate(TUniquePtr&& NewPresencePtr) { TSharedRef Presence = MakeShareable(NewPresencePtr.Release()); RosterMembers.Emplace(Presence->UserJid.GetFullPath(), Presence); OnXmppPresenceReceivedDelegate.Broadcast(ConnectionManager.AsShared(), Presence->UserJid, Presence); } void FXmppPresenceStrophe::CleanupMessages() { while (!IncomingPresenceUpdates.IsEmpty()) { TUniquePtr PresencePtr; IncomingPresenceUpdates.Dequeue(PresencePtr); FEmbeddedCommunication::AllowSleep(TickRequesterId); } } #undef TickRequesterId #endif