mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-11-08 10:09:16 +00:00
Use XMPP Roster for BuddyList
This allows the same user to keep the buddy list across clients and config resets. At the same time this allows for per lobby buddy lists which is needed as the same nickname may refer to different people. Use lower case comparison for buddy names. This is in line with the idea of local part of jid should be seen as normalized nick and is used server-side to compare user names. The migration is kept simple. I.e. only attempted if the roster is empty and the config indicates no migration was done before. Fixes: #6824 Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
This commit is contained in:
16 files changed
+313
-65
No files matched your search
@@ -566,7 +566,7 @@ terms_of_service = "0" ; Version (hash) of the Terms of Service tha
|
||||
privacy_policy = "0" ; Version (hash) of the Privacy Policy that the user has accepted
|
||||
xpartamupp = "wfgbot29" ; Name of the server-side XMPP-account that manage games
|
||||
echelon = "echelon29" ; Name of the server-side XMPP-account that manages ratings
|
||||
buddies = "," ; Comma separated list of playernames that the current user has marked as buddies
|
||||
buddies.migrated = false ; Whether a former local buddy list was already migrated to the server roster
|
||||
rememberpassword = true ; Whether to store the encrypted password in the user config
|
||||
|
||||
[lobby.columns]
|
||||
|
||||
@@ -8,13 +8,6 @@ var g_DescriptionHighlight = "orange";
|
||||
*/
|
||||
var g_DefaultLobbyRating = 1200;
|
||||
|
||||
/**
|
||||
* XEP-0172 doesn't restrict nicknames, but our lobby policy does.
|
||||
* So use this human readable delimiter to separate buddy names in the config file.
|
||||
*/
|
||||
var g_BuddyListDelimiter = ",";
|
||||
|
||||
|
||||
/**
|
||||
* Returns the nickname without the lobby rating.
|
||||
*/
|
||||
@@ -24,11 +17,6 @@ function splitRatingFromNick(playerName)
|
||||
return { "nick": result ? result[1] : playerName, "rating": result ? +result[2] : "" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Array of playernames that the current user has marked as buddies.
|
||||
*/
|
||||
var g_Buddies = Engine.ConfigDB_GetValue("user", "lobby.buddies").split(g_BuddyListDelimiter);
|
||||
|
||||
/**
|
||||
* Denotes which players are a lobby buddy of the current user.
|
||||
*/
|
||||
@@ -119,7 +107,7 @@ function formatPlayerInfo(playerDataArray, playerStates)
|
||||
playerDescriptions[teamIdx].push(sprintf(playerDescription, {
|
||||
"playerName":
|
||||
coloredText(
|
||||
(g_Buddies.indexOf(playerNick) != -1 ? g_BuddySymbol + " " : "") +
|
||||
(Engine.IsLobbyBuddy(playerNick) ? g_BuddySymbol + " " : "") +
|
||||
escapeText(playerData.Name),
|
||||
(typeof getPlayerColor === 'function' ?
|
||||
(isAI ? "white" : getPlayerColor(playerNick)) :
|
||||
|
||||
@@ -103,7 +103,8 @@ class PlayerAssignmentsController
|
||||
{
|
||||
const assignOption = Engine.ConfigDB_GetValue("user", this.ConfigAssignPlayers);
|
||||
if (assignOption == "disabled" ||
|
||||
assignOption == "buddies" && g_Buddies.indexOf(splitRatingFromNick(newAssignments[newGUID].name).nick) == -1)
|
||||
assignOption == "buddies" &&
|
||||
!Engine.IsLobbyBuddy(splitRatingFromNick(newAssignments[newGUID].name).nick))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ class BuddyButton
|
||||
this.toggleBuddyButton = Engine.GetGUIObjectByName("toggleBuddyButton");
|
||||
this.toggleBuddyButton.onPress = this.onPress.bind(this);
|
||||
|
||||
const rebuild = this.rebuild.bind(this);
|
||||
xmppMessages.registerXmppMessageHandler("system", "connected", rebuild);
|
||||
xmppMessages.registerXmppMessageHandler("system", "disconnected", rebuild);
|
||||
const rebuildAndNotify = this.rebuildAndNotify.bind(this);
|
||||
xmppMessages.registerXmppMessageHandler("system", "connected", rebuildAndNotify);
|
||||
xmppMessages.registerXmppMessageHandler("system", "disconnected", rebuildAndNotify);
|
||||
xmppMessages.registerXmppMessageHandler("roster", "change", rebuildAndNotify);
|
||||
|
||||
this.rebuild();
|
||||
}
|
||||
@@ -31,38 +32,33 @@ class BuddyButton
|
||||
|
||||
rebuild()
|
||||
{
|
||||
this.toggleBuddyButton.caption =
|
||||
g_Buddies.indexOf(this.playerName) != -1 ?
|
||||
this.UnmarkString :
|
||||
this.MarkString;
|
||||
const isBuddy = !!this.playerName && Engine.IsLobbyBuddy(this.playerName);
|
||||
|
||||
this.toggleBuddyButton.caption = isBuddy ? this.UnmarkString : this.MarkString;
|
||||
|
||||
this.toggleBuddyButton.enabled = Engine.IsXmppClientConnected() && !!this.playerName && this.playerName != g_Nickname;
|
||||
}
|
||||
|
||||
rebuildAndNotify()
|
||||
{
|
||||
this.rebuild();
|
||||
|
||||
for (const handler of this.buddyChangedHandlers)
|
||||
handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the buddy state of the selected player.
|
||||
*/
|
||||
onPress()
|
||||
{
|
||||
if (!this.playerName || this.playerName == g_Nickname || this.playerName.indexOf(g_BuddyListDelimiter) != -1)
|
||||
if (!this.playerName || this.playerName == g_Nickname)
|
||||
return;
|
||||
|
||||
const index = g_Buddies.indexOf(this.playerName);
|
||||
if (index != -1)
|
||||
g_Buddies.splice(index, 1);
|
||||
if (Engine.IsLobbyBuddy(this.playerName))
|
||||
Engine.LobbyRemoveBuddy(this.playerName);
|
||||
else
|
||||
g_Buddies.push(this.playerName);
|
||||
|
||||
Engine.ConfigDB_CreateAndSaveValue(
|
||||
"user",
|
||||
"lobby.buddies",
|
||||
g_Buddies.filter(nick => nick).join(g_BuddyListDelimiter) || g_BuddyListDelimiter
|
||||
);
|
||||
|
||||
this.rebuild();
|
||||
|
||||
for (const handler of this.buddyChangedHandlers)
|
||||
handler();
|
||||
Engine.LobbyAddBuddy(this.playerName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ class Game
|
||||
playerRatingTotal += playerNickRating.rating || g_DefaultLobbyRating;
|
||||
|
||||
// Sort games with playing buddies above games with spectating buddies
|
||||
if (hasBuddies < 2 && g_Buddies.indexOf(playerNickRating.nick) != -1)
|
||||
if (hasBuddies < 2 && Engine.IsLobbyBuddy(playerNickRating.nick))
|
||||
hasBuddies = player.Team == "observer" ? 1 : 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ class PlayerList
|
||||
|
||||
parsePlayer(sortKey, player)
|
||||
{
|
||||
player.isBuddy = g_Buddies.indexOf(player.name) != -1;
|
||||
player.isBuddy = Engine.IsLobbyBuddy(player.name);
|
||||
|
||||
switch (sortKey)
|
||||
{
|
||||
|
||||
@@ -112,5 +112,8 @@ XmppMessages.prototype.MessageTypes = {
|
||||
"profile",
|
||||
"leaderboard",
|
||||
"ratinglist"
|
||||
],
|
||||
"roster": [
|
||||
"change"
|
||||
]
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
[font="sans-bold-18"]0 A.D. Empires Ascendant Multiplayer Lobby Privacy Policy[/font]
|
||||
|
||||
[font="sans-bold-14"]Document Date:[/font] 2025-04-10
|
||||
[font="sans-bold-14"]Document Date:[/font] 2026-10-07
|
||||
|
||||
[font="sans-bold-16"]Personal data processed by Wildfire Games and purposes of processing:[/font]
|
||||
[font="sans-bold-14"]1. Player name[/font]
|
||||
@@ -38,6 +38,10 @@ Wildfire Games stores match statistics to:
|
||||
1. Verify the accuracy of the rating score and identify and enforce Terms of Service violations relating to that.
|
||||
2. Compute and publish community statistics to improve the game and the service, for example to improve the balancing of the game or to make certain aspects of the game more appealing.
|
||||
|
||||
[font="sans-bold-14"]7. Buddies[/font]
|
||||
Players can mark other players as buddies to distinguish them from all other players.
|
||||
Wildfire Games stores each player's buddy list to ensure that it is available across different computers a player might be using.
|
||||
|
||||
[font="sans-bold-14"]Security of processing:[/font]
|
||||
The transmission of personal data is secured using TLS encryption (GDPR 32).
|
||||
Personal data is protected against unintentional loss in encrypted backups for additional time (GDPR 30.1.g, GDPR 32).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -62,6 +62,8 @@
|
||||
#include <gloox/presence.h> // IWYU pragma: export
|
||||
#include <gloox/registration.h> // IWYU pragma: export
|
||||
#include <gloox/registrationhandler.h> // IWYU pragma: export
|
||||
#include <gloox/rosterlistener.h> // IWYU pragma: export
|
||||
#include <gloox/rostermanager.h> // IWYU pragma: export
|
||||
#include <gloox/stanzaextension.h> // IWYU pragma: export
|
||||
#include <gloox/tag.h> // IWYU pragma: export
|
||||
#include <gloox/util.h> // IWYU pragma: export
|
||||
|
||||
+186
-5
@@ -39,6 +39,7 @@
|
||||
#include "scriptinterface/Request.h"
|
||||
#include "scriptinterface/StructuredClone.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
@@ -51,6 +52,7 @@
|
||||
#include <js/Vector.h>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unicode/locid.h>
|
||||
@@ -60,6 +62,8 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const char* ROSTER_GROUP_NAME_BUDDIES{"buddies"};
|
||||
|
||||
//debug
|
||||
#if 1
|
||||
#define DbgXMPP(x)
|
||||
@@ -129,7 +133,7 @@ std::optional<gloox::MUCRoom> CreateMucRoom(gloox::MUCRoomHandler* handler, cons
|
||||
|
||||
class XmppClient::Impl : public gloox::ConnectionListener, public gloox::MUCRoomHandler,
|
||||
public gloox::IqHandler, public gloox::RegistrationHandler, public gloox::MessageHandler,
|
||||
public gloox::Jingle::SessionHandler, public gloox::LogHandler
|
||||
public gloox::Jingle::SessionHandler, public gloox::LogHandler, public gloox::RosterListener
|
||||
{
|
||||
public:
|
||||
// Basic
|
||||
@@ -161,6 +165,7 @@ public:
|
||||
gloox::CertStatus m_certStatus;
|
||||
bool m_initialLoadComplete;
|
||||
bool m_regOpt;
|
||||
std::set<std::string> m_Buddies;
|
||||
|
||||
private:
|
||||
/* Xmpp handlers */
|
||||
@@ -216,6 +221,37 @@ private:
|
||||
void handleSessionInitiation(gloox::Jingle::Session* session,
|
||||
const gloox::Jingle::Session::Jingle* jingle);
|
||||
|
||||
/**
|
||||
* Rebuild m_Buddies from the roster.
|
||||
*/
|
||||
void UpdateBuddies();
|
||||
|
||||
/**
|
||||
* Move a pre-roster buddy list from the config into the roster, then drop
|
||||
* it from the config. Does nothing once that migration has happened.
|
||||
*
|
||||
* Must only be called once the roster has been received, since an empty
|
||||
* roster cannot be distinguished from one without buddies.
|
||||
*/
|
||||
void MigrateBuddyList();
|
||||
|
||||
/* Roster */
|
||||
void handleItemAdded(const gloox::JID& jid) override;
|
||||
void handleItemSubscribed(const gloox::JID& /*jid*/) override {}
|
||||
void handleItemRemoved(const gloox::JID& jid) override;
|
||||
void handleItemUpdated(const gloox::JID& jid) override;
|
||||
void handleItemUnsubscribed(const gloox::JID& /*jid*/) override {}
|
||||
void handleRoster(const gloox::Roster& /*roster*/) override;
|
||||
void handleRosterPresence(const gloox::RosterItem& /*item*/, const std::string& /*resource*/, gloox::Presence::PresenceType /*presence*/, const std::string& /*msg*/) override {}
|
||||
void handleSelfPresence(const gloox::RosterItem& /*item*/, const std::string& /*resource*/, gloox::Presence::PresenceType /*presence*/, const std::string& /*msg*/) override {}
|
||||
bool handleSubscriptionRequest(const gloox::JID& /*jid*/, const std::string& /*msg*/) override { return false; }
|
||||
bool handleUnsubscriptionRequest(const gloox::JID& /*jid*/, const std::string& /*msg*/) override { return false; }
|
||||
void handleNonrosterPresence(const gloox::Presence& /*presence*/) override {}
|
||||
void handleRosterError(const gloox::IQ& iq) override;
|
||||
#if GLOOXVERSION >= 0x010100
|
||||
void handleRosterItemExchange(const gloox::JID& /*from*/, const gloox::RosterX* /*items*/) override {}
|
||||
#endif
|
||||
|
||||
template<typename... Args>
|
||||
void CreateGUIMessage(
|
||||
const std::string& type,
|
||||
@@ -223,14 +259,16 @@ private:
|
||||
const std::time_t time,
|
||||
Args const&... args);
|
||||
|
||||
public:
|
||||
struct SPlayer {
|
||||
SPlayer(const gloox::Presence::PresenceType presence, const gloox::MUCRoomRole role,
|
||||
const std::string& rating)
|
||||
: m_Presence(presence), m_Role(role), m_Rating(rating)
|
||||
const std::string& bareJid, const std::string& rating)
|
||||
: m_Presence(presence), m_Role(role), m_BareJid(bareJid), m_Rating(rating)
|
||||
{
|
||||
}
|
||||
gloox::Presence::PresenceType m_Presence;
|
||||
gloox::MUCRoomRole m_Role;
|
||||
std::string m_BareJid;
|
||||
std::string m_Rating;
|
||||
};
|
||||
using PlayerMap = std::map<std::string, SPlayer>;
|
||||
@@ -335,6 +373,8 @@ XmppClient::Impl::Impl(const Script::Interface* scriptInterface, const std::stri
|
||||
|
||||
m_client.registerMessageHandler(this);
|
||||
|
||||
m_client.rosterManager()->registerRosterListener(this, false);
|
||||
|
||||
m_registration.registerRegistrationHandler(this);
|
||||
|
||||
// Uncomment to see the raw stanzas
|
||||
@@ -428,6 +468,7 @@ void XmppClient::Impl::onDisconnect(gloox::ConnectionError error)
|
||||
m_PlayerMap.clear();
|
||||
m_PlayerMapUpdate = true;
|
||||
m_HistoricGuiMessages.clear();
|
||||
m_Buddies.clear();
|
||||
m_initialLoadComplete = false;
|
||||
|
||||
CreateGUIMessage(
|
||||
@@ -1190,7 +1231,7 @@ void XmppClient::Impl::handleMUCParticipantPresence(gloox::MUCRoom*, const gloox
|
||||
m_PlayerMap.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(participant.newNick),
|
||||
std::forward_as_tuple(presence.presence(), participant.role, std::move(m_PlayerMap.at(nick).m_Rating)));
|
||||
std::forward_as_tuple(presence.presence(), participant.role, participant.jid ? participant.jid->bare() : "", std::move(m_PlayerMap.at(nick).m_Rating)));
|
||||
else
|
||||
LOGERROR("Nickname changed to an existing nick!");
|
||||
|
||||
@@ -1282,12 +1323,16 @@ void XmppClient::Impl::handleMUCParticipantPresence(gloox::MUCRoom*, const gloox
|
||||
m_PlayerMap.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(nick),
|
||||
std::forward_as_tuple(presence.presence(), participant.role, std::string()));
|
||||
std::forward_as_tuple(presence.presence(), participant.role,
|
||||
participant.jid ? participant.jid->bare() : "",
|
||||
std::string()));
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second.m_Presence = presence.presence();
|
||||
it->second.m_Role = participant.role;
|
||||
if (participant.jid)
|
||||
it->second.m_BareJid = participant.jid->bare();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,3 +1556,139 @@ void XmppClient::Impl::handleSessionInitiation(gloox::Jingle::Session*,
|
||||
|
||||
g_NetServer->SendHolePunchingMessage(candidate.ip, candidate.port);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
//
|
||||
// Roster
|
||||
//
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
// TODO: Helper only needed till split view is supported by all compilers.
|
||||
std::vector<std::string> splitBuddyList(std::string buddyList)
|
||||
{
|
||||
std::vector<std::string> buddies{};
|
||||
size_t posStart{0};
|
||||
size_t posEnd{0};
|
||||
while ((posEnd = buddyList.find(",", posStart)) != std::string::npos)
|
||||
{
|
||||
buddies.push_back(buddyList.substr(posStart, posEnd - posStart));
|
||||
posStart = posEnd + 1;
|
||||
}
|
||||
buddies.push_back(buddyList.substr(posStart));
|
||||
return buddies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given roster item is one of the buddies tracked in the buddy list.
|
||||
*/
|
||||
bool IsBuddyRosterItem(const gloox::RosterItem& item)
|
||||
{
|
||||
const gloox::StringList groups = item.groups();
|
||||
return !item.name().empty() &&
|
||||
std::find(groups.begin(), groups.end(), ROSTER_GROUP_NAME_BUDDIES) != groups.end();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void XmppClient::AddBuddy(const std::string& nick)
|
||||
{
|
||||
DbgXMPP("Add buddy: " + nick);
|
||||
const auto it = m_Impl->m_PlayerMap.find(nick);
|
||||
|
||||
if (it == m_Impl->m_PlayerMap.end())
|
||||
LOGWARNING("XmppClient: Can't add unknown player to roster: {}", nick);
|
||||
else if (it->second.m_BareJid.empty())
|
||||
LOGWARNING("XmppClient: Anonymous player can't be added to roster: {}", nick);
|
||||
else
|
||||
m_Impl->m_client.rosterManager()->add(it->second.m_BareJid, nick, gloox::StringList{ROSTER_GROUP_NAME_BUDDIES});
|
||||
}
|
||||
|
||||
void XmppClient::RemoveBuddy(const std::string& nick)
|
||||
{
|
||||
DbgXMPP("Remove buddy: " + nick);
|
||||
const auto it = m_Impl->m_PlayerMap.find(nick);
|
||||
|
||||
if (it == m_Impl->m_PlayerMap.end())
|
||||
LOGWARNING("XmppClient: Can't remove unknown player from roster: {}", nick);
|
||||
else if (it->second.m_BareJid.empty())
|
||||
LOGWARNING("XmppClient: Anonymous player can't be removed from roster: {}", nick);
|
||||
else
|
||||
m_Impl->m_client.rosterManager()->remove(it->second.m_BareJid);
|
||||
}
|
||||
|
||||
bool XmppClient::IsBuddy(const std::string& nick) const
|
||||
{
|
||||
return m_Impl->m_Buddies.find(CStr(nick).LowerCase()) != m_Impl->m_Buddies.end();
|
||||
}
|
||||
|
||||
std::set<std::string> XmppClient::GetBuddies() const
|
||||
{
|
||||
return m_Impl->m_Buddies;
|
||||
}
|
||||
|
||||
void XmppClient::Impl::UpdateBuddies()
|
||||
{
|
||||
m_Buddies.clear();
|
||||
for (const auto& [jid, item] : *m_client.rosterManager()->roster())
|
||||
{
|
||||
if (IsBuddyRosterItem(*item))
|
||||
m_Buddies.insert(CStr(item->name()).LowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
void XmppClient::Impl::MigrateBuddyList()
|
||||
{
|
||||
if (g_ConfigDB.Get("lobby.buddies.migrated", false))
|
||||
return;
|
||||
|
||||
if (m_client.rosterManager()->roster()->empty())
|
||||
{
|
||||
const std::string buddyList{g_ConfigDB.Get("lobby.buddies", std::string{})};
|
||||
for (const std::string& nick : splitBuddyList(buddyList))
|
||||
{
|
||||
if (nick.empty())
|
||||
continue;
|
||||
|
||||
m_client.rosterManager()->add(nick + "@" + m_server, nick, gloox::StringList{ROSTER_GROUP_NAME_BUDDIES});
|
||||
}
|
||||
}
|
||||
|
||||
g_ConfigDB.RemoveValue(CFG_USER, "lobby.buddies");
|
||||
g_ConfigDB.SetValueString(CFG_USER, "lobby.buddies.migrated", "true");
|
||||
g_ConfigDB.WriteFile(CFG_USER);
|
||||
}
|
||||
|
||||
void XmppClient::Impl::handleRoster(const gloox::Roster& /*roster*/)
|
||||
{
|
||||
MigrateBuddyList();
|
||||
UpdateBuddies();
|
||||
CreateGUIMessage("roster", "change", std::time(nullptr));
|
||||
}
|
||||
|
||||
void XmppClient::Impl::handleItemAdded(const gloox::JID& /*jid*/)
|
||||
{
|
||||
UpdateBuddies();
|
||||
CreateGUIMessage("roster", "change", std::time(nullptr));
|
||||
}
|
||||
|
||||
void XmppClient::Impl::handleItemRemoved(const gloox::JID& jid)
|
||||
{
|
||||
// gloox (1.0.28) calles this before before updating the roster which means
|
||||
// calling UpdateBuddies here would still leave the removed buddy in
|
||||
// m_Buddies; drop just that entry instead.
|
||||
if (const gloox::RosterItem* item = m_client.rosterManager()->getRosterItem(jid); item && IsBuddyRosterItem(*item))
|
||||
m_Buddies.erase(CStr(item->name()).LowerCase());
|
||||
|
||||
CreateGUIMessage("roster", "change", std::time(nullptr));
|
||||
}
|
||||
|
||||
void XmppClient::Impl::handleItemUpdated(const gloox::JID& /*jid*/)
|
||||
{
|
||||
UpdateBuddies();
|
||||
CreateGUIMessage("roster", "change", std::time(nullptr));
|
||||
}
|
||||
|
||||
void XmppClient::Impl::handleRosterError(const gloox::IQ& iq)
|
||||
{
|
||||
LOGERROR("{}", iq.tag()->xml());
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include <js/Value.h>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace Script { class Interface; }
|
||||
namespace Script { class Request; }
|
||||
@@ -69,6 +71,40 @@ public:
|
||||
void SendMUCMessage(const std::string& message);
|
||||
void SendStunEndpointToHost(const std::string& ip, std::uint16_t port, const std::string& hostJID);
|
||||
|
||||
/* Roster */
|
||||
|
||||
/**
|
||||
* Add the given nickname to the buddies of the current user.
|
||||
*
|
||||
* @param nick The nickname of a player in the lobby.
|
||||
*/
|
||||
void AddBuddy(const std::string& nick);
|
||||
|
||||
/**
|
||||
* Remove the given nickname from the buddies of the current user.
|
||||
*
|
||||
* @param nick The nickname of a player in the lobby.
|
||||
*/
|
||||
void RemoveBuddy(const std::string& nick);
|
||||
|
||||
/**
|
||||
* Whether the given nickname is a buddy of the current user.
|
||||
*
|
||||
* The buddies are stored in the XMPP roster of the connected lobby. Since
|
||||
* a nickname only identifies a user within one lobby, this is meaningless
|
||||
* without a connection and always returns false in that case.
|
||||
*
|
||||
* @param nick The nickname to look up, compared case-insensitively
|
||||
*/
|
||||
bool IsBuddy(const std::string& nick) const;
|
||||
|
||||
/**
|
||||
* Get the nicknames of all buddies of the current user.
|
||||
*
|
||||
* @returns The (lowercase) nicknames of the buddies in the lobby's roster
|
||||
*/
|
||||
std::set<std::string> GetBuddies() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
const std::unique_ptr<Impl> m_Impl;
|
||||
|
||||
@@ -51,6 +51,21 @@ bool HasXmppClient()
|
||||
return g_XmppClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given nickname is a buddy of the current user.
|
||||
*
|
||||
* @param nick The nickname to look up, compared case-insensitively
|
||||
*/
|
||||
static bool IsLobbyBuddy([[maybe_unused]] const std::string& nick)
|
||||
{
|
||||
#if CONFIG2_LOBBY
|
||||
return g_XmppClient && g_XmppClient->IsBuddy(nick);
|
||||
#else
|
||||
// Without the lobby there is no roster to look the nickname up in.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetRankedGame(bool isRanked)
|
||||
{
|
||||
g_rankedGame = isRanked;
|
||||
@@ -190,6 +205,7 @@ void RegisterScriptFunctions(const Script::Request& rq)
|
||||
{
|
||||
// Lobby functions
|
||||
Script::Function::Register<&HasXmppClient>(rq, "HasXmppClient");
|
||||
Script::Function::Register<&IsLobbyBuddy>(rq, "IsLobbyBuddy");
|
||||
Script::Function::Register<&SetRankedGame>(rq, "SetRankedGame");
|
||||
#if CONFIG2_LOBBY // Allow the lobby to be disabled
|
||||
Script::Function::Register<&StartXmppClient>(rq, "StartXmppClient");
|
||||
@@ -229,6 +245,10 @@ void RegisterScriptFunctions(const Script::Request& rq)
|
||||
REGISTER_XMPP(GetRole, "LobbyGetPlayerRole");
|
||||
REGISTER_XMPP(GetRating, "LobbyGetPlayerRating");
|
||||
REGISTER_XMPP(GetSubject, "LobbyGetRoomSubject");
|
||||
|
||||
// Roster
|
||||
REGISTER_XMPP(AddBuddy, "LobbyAddBuddy");
|
||||
REGISTER_XMPP(RemoveBuddy, "LobbyRemoveBuddy");
|
||||
#undef REGISTER_XMPP
|
||||
|
||||
Script::Function::Register<&EncryptPassword>(rq, "EncryptPassword");
|
||||
|
||||
@@ -118,9 +118,10 @@ static CStr DebugName(CNetServerSession* session)
|
||||
|
||||
CNetServerWorker::CNetServerWorker(const bool continueSavedGame, std::uint16_t port,
|
||||
const bool useLobbyAuth, std::string password, std::string controllerSecret,
|
||||
std::string initAttributes) :
|
||||
std::string initAttributes, std::set<std::string> buddies) :
|
||||
m_ContinuesSavedGame{continueSavedGame},
|
||||
m_LobbyAuth{useLobbyAuth},
|
||||
m_Buddies{std::move(buddies)},
|
||||
m_ControllerSecret{std::move(controllerSecret)},
|
||||
m_Password{std::move(password)}
|
||||
{
|
||||
@@ -1078,17 +1079,9 @@ bool CNetServerWorker::OnAuthenticate(CNetServerSession* session, CFsmEvent<CNet
|
||||
}
|
||||
else if (observerLateJoin == "buddies")
|
||||
{
|
||||
std::wstringstream buddiesStream(wstring_from_utf8(
|
||||
g_ConfigDB.Get("lobby.buddies", std::string{})));
|
||||
CStrW buddy;
|
||||
while (std::getline(buddiesStream, buddy, L','))
|
||||
{
|
||||
if (buddy == usernameWithoutRating)
|
||||
{
|
||||
isRejoining = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const std::string name{usernameWithoutRating.ToUTF8().LowerCase()};
|
||||
if (server.m_Buddies.count(name) > 0)
|
||||
isRejoining = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1695,9 +1688,10 @@ void CNetServerWorker::SendHolePunchingMessage(const CStr& ipStr, std::uint16_t
|
||||
|
||||
|
||||
CNetServer::CNetServer(const bool continueSavedGame, std::uint16_t port, const bool useLobbyAuth,
|
||||
std::string password, std::string controllerSecret, std::string initAttributes) :
|
||||
std::string password, std::string controllerSecret, std::string initAttributes,
|
||||
std::set<std::string> buddies) :
|
||||
m_Worker{continueSavedGame, port, useLobbyAuth, password, std::move(controllerSecret),
|
||||
std::move(initAttributes)},
|
||||
std::move(initAttributes), std::move(buddies)},
|
||||
m_LobbyAuth{useLobbyAuth},
|
||||
m_Password{std::move(password)}
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <js/Value.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
@@ -135,7 +136,8 @@ private:
|
||||
friend class CNetServer;
|
||||
|
||||
CNetServerWorker(const bool continuesSavedGame, std::uint16_t port, const bool useLobbyAuth,
|
||||
std::string password, std::string controllerSecret, std::string initAttributes);
|
||||
std::string password, std::string controllerSecret, std::string initAttributes,
|
||||
std::set<std::string> buddies);
|
||||
~CNetServerWorker();
|
||||
|
||||
bool CheckPassword(const std::string& password, const std::string& salt) const;
|
||||
@@ -262,6 +264,13 @@ private:
|
||||
*/
|
||||
const bool m_LobbyAuth;
|
||||
|
||||
/**
|
||||
* The nicknames (lowercase) of the host's buddies at the time the game was
|
||||
* hosted, used to decide who may join after the game has started
|
||||
* (lateobserver authentication).
|
||||
*/
|
||||
const std::set<std::string> m_Buddies;
|
||||
|
||||
std::unique_ptr<ENetHost, DestroyHost> m_Host;
|
||||
std::vector<std::unique_ptr<CNetServerSession>> m_Sessions;
|
||||
|
||||
@@ -356,7 +365,8 @@ class CNetServer
|
||||
NONCOPYABLE(CNetServer);
|
||||
public:
|
||||
CNetServer(const bool isSavedGame, std::uint16_t port, const bool useLobbyAuth = false,
|
||||
std::string password = {}, std::string controllerSecret = {}, std::string initAttributes = {});
|
||||
std::string password = {}, std::string controllerSecret = {}, std::string initAttributes = {},
|
||||
std::set<std::string> buddies = {});
|
||||
|
||||
/**
|
||||
* Call from the GUI to asynchronously notify all clients that they should start loading the game.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "JSInterface_Network.h"
|
||||
|
||||
#include "lib/code_generation.h"
|
||||
#include "lib/config2.h"
|
||||
#include "lib/debug.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "lobby/XmppClient.h"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include <js/TypeDecls.h>
|
||||
#include <js/Value.h>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -109,7 +111,18 @@ void StartNetworkHost(const CStrW& playerName, const std::uint16_t serverPort, c
|
||||
HashCryptographically(password, hostJID + password + PS_SERIALIZATION_VERSION) : "";
|
||||
|
||||
const std::string secret = ps_generate_guid();
|
||||
g_NetServer = new CNetServer(continueSavedGame, serverPort, hasLobby, hashedPassword, secret);
|
||||
|
||||
// Snapshot the host's buddies from the roster of the lobby they are
|
||||
// connected to. Hosting without a lobby means there is no roster, so
|
||||
// nobody is a buddy.
|
||||
std::set<std::string> buddies;
|
||||
#if CONFIG2_LOBBY
|
||||
if (hasLobby)
|
||||
buddies = g_XmppClient->GetBuddies();
|
||||
#endif
|
||||
|
||||
g_NetServer = new CNetServer(continueSavedGame, serverPort, hasLobby, hashedPassword, secret,
|
||||
{}, buddies);
|
||||
|
||||
// Generate a secret to identify the host client.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ void TriggerAllHooks(const std::multimap<CStr, std::function<void()>>& hooks, co
|
||||
// These entries will not be printed to logfiles, so that logfiles can be shared without leaking personal or sensitive data
|
||||
const std::unordered_set<std::string> g_UnloggedEntries = {
|
||||
"lobby.password",
|
||||
"lobby.buddies",
|
||||
"lobby.buddies", // The buddy list of older versions, now stored in the server roster and removed on migration.
|
||||
"userreport.id" // authentication token for GDPR personal data requests
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user