Index: binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.js
@@ -0,0 +1,199 @@
+/**
+ * The account settings page allows the player to change some of their account settings.
+ */
+class AccountSettingsPage
+{
+ constructor(xmppMessages)
+ {
+ this.openPageHandlers = new Set();
+ this.closePageHandlers = new Set();
+
+ this.pageElement = Engine.GetGUIObjectByName("accountSettingsPage");
+ this.overlayElement = Engine.GetGUIObjectByName("as_Overlay");
+ this.requestResultElement = Engine.GetGUIObjectByName("as_RequestResult");
+ this.passwordInputElement = Engine.GetGUIObjectByName("as_PasswordInput");
+ this.passwordInputConfirmElement = Engine.GetGUIObjectByName("as_PasswordInputConfirm");
+
+ this.pageElement.onTick = () => this.onTick();
+
+ this.currentRequest = undefined;
+
+ xmppMessages.registerXmppMessageHandler("system", "registered", x => this.onSystemMessage(x));
+ xmppMessages.registerXmppMessageHandler("system", "error", x => this.onSystemMessage(x));
+
+ Engine.GetGUIObjectByName("as_ChangePasswordBtn").onPress = () => this.onPressChangePassword();
+ Engine.GetGUIObjectByName("as_Close").onPress = () => this.onPressClose();
+ }
+
+ registerOpenPageHandler(handler)
+ {
+ this.openPageHandlers.add(handler);
+ }
+
+ registerClosePageHandler(handler)
+ {
+ this.closePageHandlers.add(handler);
+ }
+
+ onTick()
+ {
+ if (this.currentRequest)
+ updateTimers();
+ }
+
+ openPage()
+ {
+ this.currentRequest = undefined;
+ this.requestResultElement.caption = "";
+ this.pageElement.hidden = false;
+ Engine.SetGlobalHotkey("cancel", "Press", () => this.onPressClose());
+ }
+
+ onSystemMessage(message)
+ {
+ if (!this.currentRequest)
+ return;
+
+ if (this.currentRequest.type === "changePassword" && message.level === 'registered')
+ this.currentRequest.succeed(message);
+ else if (message.level === 'error')
+ this.currentRequest.fail(message.text);
+ }
+
+ handleRequestSuccess()
+ {
+ this.overlayElement.hidden = true;
+
+ if (!this.currentRequest?.resolved)
+ {
+ error("handleRequestSuccess called on unresolved request");
+ return;
+ }
+
+ if (this.currentRequest.type === "changePassword")
+ {
+ this.requestResultElement.caption = translate("Password changed successfully.");
+ if (Engine.ConfigDB_GetValue("user", "lobby.rememberpassword") == "true")
+ Engine.ConfigDB_CreateValue("user", "lobby.password", this.currentRequest.newPassword);
+ else
+ Engine.ConfigDB_RemoveValue("user", "lobby.password");
+ Engine.ConfigDB_SaveChanges("user");
+ }
+ }
+
+ handleRequestFailure()
+ {
+ this.overlayElement.hidden = true;
+
+ if (!this.currentRequest?.resolved)
+ {
+ error("handleRequestFailure called on unresolved request");
+ return;
+ }
+ this.requestResultElement.caption = this.currentRequest.message;
+
+ if (this.currentRequest.type === "changePassword")
+ {
+ this.passwordInputElement.caption = "";
+ this.passwordInputConfirmElement.caption = "";
+ }
+ }
+
+ onPressChangePassword()
+ {
+ if (this.currentRequest && !this.currentRequest.resolved)
+ return;
+
+ this.currentRequest = new PasswordChangeRequest(() => this.handleRequestSuccess(), () => this.handleRequestFailure());
+
+ let newPass = this.passwordInputElement.caption;
+ if (newPass.length < minimumPasswordLength)
+ return this.currentRequest.fail(translate("Error: Please choose a longer password"))
+
+ let confirm = this.passwordInputConfirmElement.caption;
+ if (newPass !== confirm)
+ return this.currentRequest.fail(translate("Error: Passwords do not match"));
+
+ let usn = Engine.LobbyGetJID();
+ let atIndex = usn.indexOf("@");
+ if (atIndex == -1)
+ {
+ // Probably can't happen too easily, so error out.
+ error("Invalid JID");
+ return this.currentRequest.fail(translate("Error: Invalid JID, cannot change password."));
+ }
+
+ newPass = Engine.EncryptPassword(newPass, usn.substring(0, atIndex).toLowerCase());
+ this.overlayElement.hidden = false;
+ Engine.LobbyChangePassword(newPass);
+ this.currentRequest.newPassword = newPass;
+ this.currentRequest.startTimeout(translate("Error: Request timed out."));
+ }
+
+ onPressClose()
+ {
+ this.pageElement.hidden = true;
+
+ for (let handler of this.closePageHandlers)
+ handler();
+ }
+}
+
+/**
+ * TODO: convert this to a proper promise ?
+ */
+class AccountSettingsRequest
+{
+ message = "";
+ status = "pending";
+ timeout = undefined;
+
+ constructor(success, failure)
+ {
+ this.onSuccess = success;
+ this.onFailure = failure;
+ }
+
+ _resolveCommon(message)
+ {
+ this.message = message;
+ if (!this.timeout)
+ return;
+ clearTimeout(this.timeout);
+ this.timeout = undefined;
+ }
+
+ succeed(message)
+ {
+ this._resolveCommon(message);
+ this.status = "success";
+ this.onSuccess();
+ }
+
+ fail(message)
+ {
+ this._resolveCommon(message);
+ this.status = "failed";
+ this.onFailure();
+ }
+
+ startTimeout(message, timeout = 30000)
+ {
+ this.timeout = setTimeout(() => this.fail(message), timeout);
+ }
+
+ get resolved()
+ {
+ return this.status !== "pending";
+ }
+}
+
+class PasswordChangeRequest extends AccountSettingsRequest
+{
+ newPassword = "";
+ constructor(success, failure)
+ {
+ super(success, failure);
+ this.type = "changePassword";
+ }
+}
Index: binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/lobby/AccountSettingsPage/AccountSettingsPage.xml
@@ -0,0 +1,46 @@
+
+
Index: binaries/data/mods/public/gui/lobby/LobbyHandler.js
===================================================================
--- binaries/data/mods/public/gui/lobby/LobbyHandler.js
+++ binaries/data/mods/public/gui/lobby/LobbyHandler.js
@@ -7,9 +7,10 @@
{
this.xmppMessages = new XmppMessages();
+ this.accountSettingsPage = new AccountSettingsPage(this.xmppMessages);
this.profilePage = new ProfilePage(this.xmppMessages);
this.leaderboardPage = new LeaderboardPage(this.xmppMessages);
- this.lobbyPage = new LobbyPage(dialog, this.xmppMessages, this.leaderboardPage, this.profilePage);
+ this.lobbyPage = new LobbyPage(dialog, this.xmppMessages, this.leaderboardPage, this.profilePage, this.accountSettingsPage);
this.xmppMessages.processHistoricMessages();
Index: binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/AccountSettingsButton.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/lobby/LobbyPage/Buttons/AccountSettingsButton.js
@@ -0,0 +1,39 @@
+/**
+ * This class manages the button that enables the player to change their account settings.
+ * Takes the spot of the the buddy button when the user selects their own name.
+ */
+class AccountSettingsButton
+{
+ constructor(xmppMessages, page)
+ {
+ this.button = Engine.GetGUIObjectByName("accountSettingsButton");
+ this.button.onPress = () => this.onPress();
+ this.visible = false;
+
+ this.page = page;
+
+ xmppMessages.registerXmppMessageHandler("system", "connected", () => this.rebuild());
+ xmppMessages.registerXmppMessageHandler("system", "disconnected", () => this.rebuild());
+
+ this.rebuild();
+ }
+
+ onPlayerSelectionChange(playerName)
+ {
+ this.visible = playerName === g_Nickname;
+ this.rebuild();
+ }
+
+ rebuild()
+ {
+ this.button.caption = this.Caption;
+ this.button.hidden = !Engine.IsXmppClientConnected() || !this.visible;
+ }
+
+ onPress()
+ {
+ this.page.openPage();
+ }
+}
+
+AccountSettingsButton.prototype.Caption = translate("Account settings");
Index: binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
===================================================================
--- binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
+++ binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.js
@@ -4,13 +4,14 @@
*/
class LobbyPage
{
- constructor(dialog, xmppMessages, leaderboardPage, profilePage)
+ constructor(dialog, xmppMessages, leaderboardPage, profilePage, accountSettingsPage)
{
Engine.ProfileStart("Create LobbyPage");
let mapCache = new MapCache();
let buddyButton = new BuddyButton(xmppMessages);
+ let accountSettingsButton = new AccountSettingsButton(xmppMessages, accountSettingsPage);
let gameList = new GameList(xmppMessages, buddyButton, mapCache);
- let playerList = new PlayerList(xmppMessages, buddyButton, gameList);
+ let playerList = new PlayerList(xmppMessages, buddyButton, accountSettingsButton, gameList);
this.lobbyPage = {
"buttons": {
Index: binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
===================================================================
--- binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
+++ binaries/data/mods/public/gui/lobby/LobbyPage/LobbyPage.xml
@@ -24,6 +24,7 @@
+
Index: binaries/data/mods/public/gui/lobby/LobbyPage/PlayerList.js
===================================================================
--- binaries/data/mods/public/gui/lobby/LobbyPage/PlayerList.js
+++ binaries/data/mods/public/gui/lobby/LobbyPage/PlayerList.js
@@ -4,7 +4,7 @@
*/
class PlayerList
{
- constructor(xmppMessages, buddyButton, gameList)
+ constructor(xmppMessages, buddyButton, accountSettingsButton, gameList)
{
this.gameList = gameList;
this.selectedPlayer = undefined;
@@ -34,6 +34,7 @@
buddyButton.registerBuddyChangeHandler(this.onBuddyChange.bind(this));
xmppMessages.registerPlayerListUpdateHandler(this.rebuildPlayerList.bind(this));
this.registerSelectionChangeHandler(buddyButton.onPlayerSelectionChange.bind(buddyButton));
+ this.registerSelectionChangeHandler(accountSettingsButton.onPlayerSelectionChange.bind(accountSettingsButton));
this.registerMouseLeftDoubleClickItemHandler(buddyButton.onPress.bind(buddyButton));
this.rebuildPlayerList();
Index: binaries/data/mods/public/gui/lobby/lobby.xml
===================================================================
--- binaries/data/mods/public/gui/lobby/lobby.xml
+++ binaries/data/mods/public/gui/lobby/lobby.xml
@@ -9,6 +9,7 @@
+
Index: binaries/data/mods/public/gui/lobby/password.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/lobby/password.js
@@ -0,0 +1,4 @@
+/**
+ * Common definitions for the lobby password.
+ */
+const minimumPasswordLength = 8;
Index: binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
===================================================================
--- binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
+++ binaries/data/mods/public/gui/prelobby/common/credentials/credentials.js
@@ -19,7 +19,7 @@
translateWithContext("register", "Please enter your password") :
translateWithContext("login", "Please enter your password");
- if (register && password.length < 8)
+ if (register && password.length < minimumPasswordLength)
return translate("Please choose a longer password");
return "";
Index: binaries/data/mods/public/gui/prelobby/login/login.xml
===================================================================
--- binaries/data/mods/public/gui/prelobby/login/login.xml
+++ binaries/data/mods/public/gui/prelobby/login/login.xml
@@ -6,6 +6,7 @@
+
Index: binaries/data/mods/public/gui/prelobby/register/register.xml
===================================================================
--- binaries/data/mods/public/gui/prelobby/register/register.xml
+++ binaries/data/mods/public/gui/prelobby/register/register.xml
@@ -6,6 +6,7 @@
+
Index: source/lobby/IXmppClient.h
===================================================================
--- source/lobby/IXmppClient.h
+++ source/lobby/IXmppClient.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -43,6 +43,7 @@
virtual void SetNick(const std::string& nick) = 0;
virtual std::string GetNick() const = 0;
virtual std::string GetJID() const = 0;
+ virtual void ChangePassword(const std::string& newPassword) = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;
Index: source/lobby/XmppClient.h
===================================================================
--- source/lobby/XmppClient.h
+++ source/lobby/XmppClient.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -35,7 +35,7 @@
struct CertInfo;
}
-class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler
+class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler, public gloox::LogHandler
{
NONCOPYABLE(XmppClient);
@@ -63,6 +63,7 @@
gloox::CertStatus m_certStatus;
bool m_initialLoadComplete;
bool m_isConnected;
+ bool m_regOpt;
public:
// Basic
@@ -93,6 +94,7 @@
void SetNick(const std::string& nick);
std::string GetNick() const;
std::string GetJID() const;
+ void ChangePassword(const std::string& newPassword);
void kick(const std::string& nick, const std::string& reason);
void ban(const std::string& nick, const std::string& reason);
void SetPresence(const std::string& presence);
Index: source/lobby/XmppClient.cpp
===================================================================
--- source/lobby/XmppClient.cpp
+++ source/lobby/XmppClient.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -81,6 +81,7 @@
m_client(nullptr),
m_mucRoom(nullptr),
m_registration(nullptr),
+ m_regOpt(regOpt),
m_username(sUsername),
m_password(sPassword),
m_room(sRoom),
@@ -151,8 +152,11 @@
m_client->registerMessageHandler(this);
+ m_registration = new glooxwrapper::Registration(m_client);
+ m_registration->registerRegistrationHandler(this);
+
// Uncomment to see the raw stanzas
- //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
+ // m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, static_cast(this) );
if (!regOpt)
{
@@ -161,12 +165,6 @@
// Get room history.
m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
}
- else
- {
- // Registration
- m_registration = new glooxwrapper::Registration(m_client);
- m_registration->registerRegistrationHandler(this);
- }
m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
// Register plugins to allow gloox parse them in incoming sessions
@@ -254,7 +252,7 @@
m_mucRoom->join();
}
- if (m_registration)
+ if (m_regOpt)
m_registration->fetchRegistrationFields();
}
@@ -538,8 +536,6 @@
CreateGUIMessage("system", "registered", std::time(nullptr));
else
CreateGUIMessage("system", "error", std::time(nullptr), "text", result);
-
- disconnect();
}
void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
@@ -1193,6 +1189,16 @@
return m_client->getJID().to_string();
}
+/**
+ * Change password for authenticated user.
+ *
+ * @param newPassword New password
+ */
+void XmppClient::ChangePassword(const std::string& newPassword)
+{
+ m_registration->changePassword(m_username, newPassword);
+}
+
/**
* Kick a player from the current room.
*
Index: source/lobby/glooxwrapper/glooxwrapper.h
===================================================================
--- source/lobby/glooxwrapper/glooxwrapper.h
+++ source/lobby/glooxwrapper/glooxwrapper.h
@@ -573,6 +573,7 @@
~Registration();
void fetchRegistrationFields();
bool createAccount(int fields, const RegistrationFields& values);
+ void changePassword(const string& username, const string& password);
void registerRegistrationHandler(RegistrationHandler* rh);
};
Index: source/lobby/glooxwrapper/glooxwrapper.cpp
===================================================================
--- source/lobby/glooxwrapper/glooxwrapper.cpp
+++ source/lobby/glooxwrapper/glooxwrapper.cpp
@@ -703,6 +703,11 @@
return m_Wrapped->createAccount(fields, valuesUnwrapped);
}
+void glooxwrapper::Registration::changePassword(const string& username, const string& password)
+{
+ m_Wrapped->changePassword(username.to_string(), password.to_string());
+}
+
void glooxwrapper::Registration::registerRegistrationHandler(RegistrationHandler* rh)
{
gloox::RegistrationHandler* handler = new RegistrationHandlerWrapper(rh);
Index: source/lobby/scripting/JSInterface_Lobby.cpp
===================================================================
--- source/lobby/scripting/JSInterface_Lobby.cpp
+++ source/lobby/scripting/JSInterface_Lobby.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2021 Wildfire Games.
+/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -218,6 +218,7 @@
REGISTER_XMPP(SetNick, "LobbySetNick");
REGISTER_XMPP(GetNick, "LobbyGetNick");
REGISTER_XMPP(GetJID, "LobbyGetJID");
+ REGISTER_XMPP(ChangePassword, "LobbyChangePassword");
REGISTER_XMPP(kick, "LobbyKick");
REGISTER_XMPP(ban, "LobbyBan");
REGISTER_XMPP(GetPresence, "LobbyGetPlayerPresence");