From e81deff4e94aa7246e3458e743da8dc7c485fbf0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 30 Sep 2019 10:42:00 -0700 Subject: [PATCH] Add signup to qt launcher, and cleanup --- launchers/qt/CMakeLists.txt | 10 +- .../qml/HFBase/CreateAccountBase.qml | 10 +- .../qt/resources/qml/HFBase/LoginBase.qml | 5 +- launchers/qt/src/Helper.cpp | 44 ++++ launchers/qt/src/Helper.h | 16 ++ launchers/qt/src/Helper_windows.cpp | 2 +- launchers/qt/src/LauncherState.cpp | 246 +++++++++--------- launchers/qt/src/LauncherState.h | 36 ++- launchers/qt/src/LoginRequest.cpp | 73 ++++++ launchers/qt/src/LoginRequest.h | 49 ++++ launchers/qt/src/SignupRequest.cpp | 79 ++++++ launchers/qt/src/SignupRequest.h | 38 +++ launchers/qt/src/UserSettingsRequest.cpp | 67 +++++ launchers/qt/src/UserSettingsRequest.h | 43 +++ launchers/qt/src/main.cpp | 10 +- 15 files changed, 575 insertions(+), 153 deletions(-) create mode 100644 launchers/qt/src/LoginRequest.cpp create mode 100644 launchers/qt/src/LoginRequest.h create mode 100644 launchers/qt/src/SignupRequest.cpp create mode 100644 launchers/qt/src/SignupRequest.h create mode 100644 launchers/qt/src/UserSettingsRequest.cpp create mode 100644 launchers/qt/src/UserSettingsRequest.h diff --git a/launchers/qt/CMakeLists.txt b/launchers/qt/CMakeLists.txt index 169240c647..2cd6ff5456 100644 --- a/launchers/qt/CMakeLists.txt +++ b/launchers/qt/CMakeLists.txt @@ -127,8 +127,14 @@ set(src_files src/LauncherState.cpp src/LauncherWindow.h src/LauncherWindow.cpp - src/PathUtils.cpp + src/LoginRequest.h + src/LoginRequest.cpp + src/SignupRequest.h + src/SignupRequest.cpp + src/UserSettingsRequest.h + src/UserSettingsRequest.cpp src/PathUtils.h + src/PathUtils.cpp src/Unzipper.h src/Unzipper.cpp src/Helper.h @@ -159,7 +165,7 @@ set(TARGET_NAME ${PROJECT_NAME}) set_packaging_parameters() if (WIN32) - add_executable(${PROJECT_NAME} ${src_files} build/resources.qrc) + add_executable(${PROJECT_NAME} ${src_files} ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) elseif (APPLE) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) diff --git a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml index c18d14b314..3c98cebd33 100644 --- a/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml +++ b/launchers/qt/resources/qml/HFBase/CreateAccountBase.qml @@ -47,7 +47,7 @@ Item { font.family: "Graphik" font.pixelSize: 14 color: "#C4C4C4" - text: "Use the email address that you regisetered with." + text: "Use the email address that you regisetered with. " + LauncherState.lastSignupError anchors { left: root.left leftMargin: root.marginLeft @@ -57,7 +57,7 @@ Item { } HFTextField { - id: organization + id: email width: 430 height: 50 font.family: "Graphik" @@ -83,7 +83,7 @@ Item { color: "#7e8c81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) anchors { - top: organization.bottom + top: email.bottom left: root.left leftMargin: root.marginLeft topMargin: 18 @@ -157,7 +157,7 @@ Item { topMargin: 21 } - onClicked: LauncherState.login(username.text, passwordField.text) + onClicked: LauncherState.signup(email.text, username.text, passwordField.text, displayName.text) } @@ -181,7 +181,7 @@ Item { onClicked: { console.log("clicked"); - root.parent.source = PathUtils.resourcePath("qml/Login.qml"); + LauncherState.gotoLogin(); } } } diff --git a/launchers/qt/resources/qml/HFBase/LoginBase.qml b/launchers/qt/resources/qml/HFBase/LoginBase.qml index 9d468fc5c8..deb21ad085 100644 --- a/launchers/qt/resources/qml/HFBase/LoginBase.qml +++ b/launchers/qt/resources/qml/HFBase/LoginBase.qml @@ -109,7 +109,6 @@ Item { placeholderText: "Display name" color: "#7E8C81" seperatorColor: Qt.rgba(1, 1, 1, 0.3) - echoMode: TextInput.Password anchors { top: displayText.bottom horizontalCenter: instruction.horizontalCenter @@ -132,7 +131,7 @@ Item { topMargin: 25 } - onClicked: LauncherState.login(username.text, password.text) + onClicked: LauncherState.login(username.text, password.text, displayName.text) } Text { @@ -155,7 +154,7 @@ Item { onClicked: { console.log("clicked"); - root.parent.source = PathUtils.resourcePath("qml/Login.qml"); + LauncherState.gotoSignup(); } } } diff --git a/launchers/qt/src/Helper.cpp b/launchers/qt/src/Helper.cpp index f056676d43..729824da3f 100644 --- a/launchers/qt/src/Helper.cpp +++ b/launchers/qt/src/Helper.cpp @@ -25,3 +25,47 @@ void swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPat exit(0); } } + +QString getHTTPUserAgent() { +#if defined(Q_OS_WIN) + return "HQLauncher/fixme (Windows)"; +#elif defined(Q_OS_MACOS) + return "HQLauncher/fixme (MacOS)"; +#else +#error Unsupported platform +#endif +} + +const QString& getInterfaceSharedMemoryName() { + static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME"); + return applicationName; +} + +//#ifdef Q_OS_WIN +//#include +//#include +//#include +//#include +//#include +//#include +// +//bool isProcessRunning(const wchar_t *processName, int& processID) { +// bool exists = false; +// PROCESSENTRY32 entry; +// entry.dwSize = sizeof(PROCESSENTRY32); +// +// HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); +// +// if (Process32First(snapshot, &entry)) { +// while (Process32Next(snapshot, &entry)) { +// if (!_wcsicmp(entry.szExeFile, processName)) { +// exists = true; +// processID = entry.th32ProcessID; +// break; +// } +// } +// } +// CloseHandle(snapshot); +// return exists; +//} +//#endif diff --git a/launchers/qt/src/Helper.h b/launchers/qt/src/Helper.h index 7d1f54a949..a0b4eb55fe 100644 --- a/launchers/qt/src/Helper.h +++ b/launchers/qt/src/Helper.h @@ -1,6 +1,14 @@ #include #include +//#define USE_STAGING + +#ifdef USE_STAGING +const QString METAVERSE_API_DOMAIN{ "https://staging.highfidelity.com" }; +#else +const QString METAVERSE_API_DOMAIN{ "https://metaverse.highfidelity.com" }; +#endif + void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride, const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString()); @@ -11,3 +19,11 @@ void swapLaunchers(const QString& oldLauncherPath = QString(), const QString& ne #ifdef Q_OS_MAC bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory); #endif + +QString getHTTPUserAgent(); + +const QString& getInterfaceSharedMemoryName(); + +//#ifdef Q_OS_WIN +//bool isProcessRunning(const wchar_t *processName, int& processID) { +//#endif diff --git a/launchers/qt/src/Helper_windows.cpp b/launchers/qt/src/Helper_windows.cpp index cb3671f274..bc5185d9f6 100644 --- a/launchers/qt/src/Helper_windows.cpp +++ b/launchers/qt/src/Helper_windows.cpp @@ -9,7 +9,7 @@ void launchClient(const QString& clientPath, const QString& homePath, const QStr // TODO Fix parameters QString params = "--url \"" + homePath + "\"" + " --setBookmark \"hqhome=" + homePath + "\"" - + " --defaultScriptsOverride \"" + defaultScriptsPath + "\"" + + " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\"" + " --displayName \"" + displayName + "\"" + " --cache \"" + contentCachePath + "\""; diff --git a/launchers/qt/src/LauncherState.cpp b/launchers/qt/src/LauncherState.cpp index 6b5f8bd35c..3901a5c4ab 100644 --- a/launchers/qt/src/LauncherState.cpp +++ b/launchers/qt/src/LauncherState.cpp @@ -30,8 +30,6 @@ #include -const QString METAVERSE_API_URL{ "https://metaverse.highfidelity.com" }; -const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; QString LauncherState::getContentCachePath() const { return _launcherDirectory.filePath("cache"); @@ -70,7 +68,7 @@ bool LatestBuilds::getBuild(QString tag, Build* outBuild) { } static const std::array QML_FILE_FOR_UI_STATE = - { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "DisplayName.qml", + { { "SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml", "DisplayName.qml", "qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } }; void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { @@ -82,7 +80,7 @@ void LauncherState::ASSERT_STATE(LauncherState::ApplicationState state) { } } -void LauncherState::ASSERT_STATE(std::vector states) { +void LauncherState::ASSERT_STATE(const std::vector& states) { for (auto state : states) { if (_applicationState == state) { return; @@ -120,6 +118,9 @@ LauncherState::UIState LauncherState::getUIState() const { case ApplicationState::WaitingForLogin: case ApplicationState::RequestingLogin: return LOGIN_SCREEN; + case ApplicationState::WaitingForSignup: + case ApplicationState::RequestingSignup: + return SIGNUP_SCREEN; case ApplicationState::DownloadingClient: case ApplicationState::InstallingClient: case ApplicationState::DownloadingContentCache: @@ -240,33 +241,11 @@ void LauncherState::getCurrentClientVersion() { QProcess client; QEventLoop loop; - //connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit); - connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit); - /* - connect(&client, QOverload::of(&QProcess::finished), [&]() { - qDebug() << "Finished"; - }); - connect(&client, &QProcess::errorOccurred, [&](QProcess::ProcessError err) { - qDebug() << "Error occurred" << err << client.error(); - }); - connect(&client, &QProcess::started, [&]() { - qDebug() << "Started"; - }); - connect(&client, &QProcess::stateChanged, [&]() { - qDebug() << "State changed " << client.state(); - }); - */ + connect(&client, QOverload::of(&QProcess::finished), &loop, &QEventLoop::exit, Qt::QueuedConnection); + connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection); - //qDebug() << "Starting client"; client.start(getClientExecutablePath(), { "--version" }); - //qDebug() << "Started" << client.error(); - - if (client.state() != QProcess::NotRunning) { - //qDebug() << "Starting loop"; - loop.exec(); - } else { - qDebug() << "Not waiting for client, there was an error starting it: " << client.error(); - } + loop.exec(); // TODO Handle errors auto output = client.readAllStandardOutput(); @@ -282,132 +261,140 @@ void LauncherState::getCurrentClientVersion() { } qDebug() << "Current client version is: " << _currentClientVersion; - setApplicationState(ApplicationState::WaitingForLogin); + setApplicationState(ApplicationState::WaitingForSignup); } -QString getUserAgent() { -#if defined(Q_OS_WIN) - return "HQLauncher/fixme (Windows)"; -#elif defined(Q_OS_MACOS) - return "HQLauncher/fixme (MacOS)"; -#else -#error Unsupported platform -#endif + +void LauncherState::gotoSignup() { + if (_applicationState == ApplicationState::WaitingForLogin) { + setApplicationState(ApplicationState::WaitingForSignup); + } else { + qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; + } } -void LauncherState::login(QString username, QString password) { +void LauncherState::gotoLogin() { + if (_applicationState == ApplicationState::WaitingForSignup) { + setApplicationState(ApplicationState::WaitingForLogin); + } else { + qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState; + } +} + +void LauncherState::signup(QString email, QString username, QString password, QString displayName) { + ASSERT_STATE(ApplicationState::WaitingForSignup); + + _username = username; + _password = password; + + setApplicationState(ApplicationState::RequestingSignup); + + auto signupRequest = new SignupRequest(); + + _displayName = displayName; + + { + _lastSignupError = SignupRequest::Error::None; + emit lastSignupErrorChanged(); + } + + QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest] { + signupRequest->deleteLater(); + + + _lastSignupError = signupRequest->getError(); + emit lastSignupErrorChanged(); + + if (_lastSignupError != SignupRequest::Error::None) { + setApplicationStateError("Failed to sign up"); + return; + } + + setApplicationState(ApplicationState::RequestingLoginAfterSignup); + + // After successfully signing up, attempt to login + auto loginRequest = new LoginRequest(); + + connect(loginRequest, &LoginRequest::finished, this, [this, loginRequest]() { + ASSERT_STATE(ApplicationState::RequestingLoginAfterSignup); + + loginRequest->deleteLater(); + + auto err = loginRequest->getError(); + if (err != LoginRequest::Error::None) { + setApplicationStateError("Failed to login"); + return; + } + + + _loginResponse = loginRequest->getToken(); + _loginTokenResponse = loginRequest->getRawToken(); + + requestSettings(); + }); + + setApplicationState(ApplicationState::RequestingLoginAfterSignup); + loginRequest->send(_networkAccessManager, _username, _password); + }); + signupRequest->send(_networkAccessManager, email, username, password); +} + + +void LauncherState::login(QString username, QString password, QString displayName) { ASSERT_STATE(ApplicationState::WaitingForLogin); setApplicationState(ApplicationState::RequestingLogin); + _displayName = displayName; + qDebug() << "Got login: " << username << password; - auto request = new QNetworkRequest(QUrl(METAVERSE_API_URL + "/oauth/token")); + auto request = new LoginRequest(); - request->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); - request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QUrlQuery query; - query.addQueryItem("grant_type", "password"); - query.addQueryItem("username", username); - query.addQueryItem("password", password); - query.addQueryItem("scope", "owner"); + connect(request, &LoginRequest::finished, this, [this, request]() { + ASSERT_STATE(ApplicationState::RequestingLogin); - auto reply = _networkAccessManager.post(*request, query.toString().toUtf8()); - QObject::connect(reply, &QNetworkReply::finished, this, &LauncherState::receivedLoginReply); -} + request->deleteLater(); -Q_INVOKABLE void LauncherState::receivedLoginReply() { - ASSERT_STATE(ApplicationState::RequestingLogin); + auto err = request->getError(); + if (err != LoginRequest::Error::None) { + setApplicationStateError("Failed to login"); + return; + } - // TODO Check for errors - auto reply = static_cast(sender()); + _loginResponse = request->getToken(); + _loginTokenResponse = request->getRawToken(); - if (reply->error()) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } + requestSettings(); + }); - auto data = reply->readAll(); - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - auto root = doc.object(); - - if (!root.contains("access_token") - || !root.contains("token_type") - || !root.contains("expires_in") - || !root.contains("refresh_token") - || !root.contains("scope") - || !root.contains("created_at")) { - - setApplicationState(ApplicationState::UnexpectedError); - return; - } - - _loginResponse.accessToken = root["access_token"].toString(); - _loginResponse.refreshToken = root["refresh_token"].toString(); - _loginResponse.tokenType = root["token_type"].toString(); - - qDebug() << "Got response for login: " << data; - _loginTokenResponse = data; - - requestSettings(); + request->send(_networkAccessManager, username, password); } void LauncherState::requestSettings() { // TODO Request settings if already logged in + qDebug() << "Requesting settings"; - QUrl lockerURL = METAVERSE_API_URL; - lockerURL.setPath("/api/v1/user/locker"); + auto request = new UserSettingsRequest(); - auto lockerRequest = new QNetworkRequest(lockerURL); - lockerRequest->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - lockerRequest->setHeader(QNetworkRequest::UserAgentHeader, getUserAgent()); - lockerRequest->setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(_loginResponse.accessToken).toUtf8()); - - QNetworkReply* lockerReply = _networkAccessManager.get(*lockerRequest); - connect(lockerReply, &QNetworkReply::finished, this, &LauncherState::receivedSettingsReply); -} - -void LauncherState::receivedSettingsReply() { - auto reply = static_cast(sender()); - qDebug() << "Got reply: " << reply->error(); - if (reply->error()) { - setApplicationState(ApplicationState::UnexpectedError); - return; - } - auto data = reply->readAll(); - qDebug() << "Settings: " << data; - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(data, &parseError); - - if (parseError.error != QJsonParseError::NoError) { - qDebug() << "Error parsing settings"; - setApplicationStateError("Error retreiving settings"); - return; - } - - auto root = doc.object(); - if (root["status"] != "success") { - qDebug() << "Status is not \"success\""; - setApplicationStateError("Error retreiving settings"); - return; - } - - _homeLocation = "hifi://hq"; - if (root["data"].toObject().contains("home_location")) { - auto homeLocation = root["data"].toObject()["home_location"]; - if (homeLocation.isString()) { - _homeLocation = homeLocation.toString(); + connect(request, &UserSettingsRequest::finished, this, [this, request]() { + auto userSettings = request->getUserSettings(); + if (userSettings.homeLocation.isEmpty()) { + _homeLocation = "hifi://hq"; + _contentCacheURL = ""; + } else { + _homeLocation = userSettings.homeLocation; auto host = QUrl(_homeLocation).host(); _contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip"; - qDebug() << "Home location is: " << _homeLocation; - qDebug() << "Content cache url is: " << _contentCacheURL; } - } - //qDebug() << "Home:" << _homeLocation << QUrl(_homeLocation).host(); + qDebug() << "Home location is: " << _homeLocation; + qDebug() << "Content cache url is: " << _contentCacheURL; - downloadClient(); + downloadClient(); + }); + + request->send(_networkAccessManager, _loginResponse); } void LauncherState::downloadClient() { @@ -683,10 +670,9 @@ void LauncherState::launchClient() { defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js"); #endif - QString displayName = "fixMe"; QString contentCachePath = _launcherDirectory.filePath("cache"); - ::launchClient(clientPath, _homeLocation, QDir::toNativeSeparators(defaultScriptsPath), displayName, contentCachePath, _loginTokenResponse); + ::launchClient(clientPath, _homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse); } void LauncherState::setApplicationStateError(QString errorMessage) { diff --git a/launchers/qt/src/LauncherState.h b/launchers/qt/src/LauncherState.h index 36dab7d98d..16137e7b87 100644 --- a/launchers/qt/src/LauncherState.h +++ b/launchers/qt/src/LauncherState.h @@ -7,6 +7,10 @@ #include #include +#include "LoginRequest.h" +#include "SignupRequest.h" +#include "UserSettingsRequest.h" + struct Build { QString tag; int latestVersion; @@ -22,18 +26,13 @@ struct LatestBuilds { Build launcherBuild; }; -struct LoginResponse { - QString accessToken; - QString tokenType; - QString refreshToken; -}; - class LauncherState : public QObject { Q_OBJECT Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged); Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged); Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged); + Q_PROPERTY(SignupRequest::Error lastSignupError MEMBER _lastSignupError NOTIFY lastSignupErrorChanged); public: LauncherState(); @@ -41,6 +40,7 @@ public: enum UIState { SPLASH_SCREEN = 0, + SIGNUP_SCREEN, LOGIN_SCREEN, DISPLAY_NAME_SCREEN, DOWNLOAD_SCREEN, @@ -60,6 +60,10 @@ public: WaitingForLogin, RequestingLogin, + WaitingForSignup, + RequestingSignup, + RequestingLoginAfterSignup, + DownloadingClient, DownloadingLauncher, DownloadingContentCache, @@ -85,7 +89,7 @@ public: Q_INVOKABLE QString getCurrentUISource() const; void ASSERT_STATE(LauncherState::ApplicationState state); - void ASSERT_STATE(std::vector states); + void ASSERT_STATE(const std::vector& states); static void declareQML(); @@ -98,17 +102,21 @@ public: void setApplicationState(ApplicationState state); ApplicationState getApplicationState() const; + Q_INVOKABLE void gotoSignup(); + Q_INVOKABLE void gotoLogin(); + // Request builds void requestBuilds(); Q_INVOKABLE void receivedBuildsReply(); + // Signup + Q_INVOKABLE void signup(QString email, QString username, QString password, QString displayName); // Login - Q_INVOKABLE void login(QString username, QString password); - Q_INVOKABLE void receivedLoginReply(); + Q_INVOKABLE void login(QString username, QString password, QString displayName); + // Request Settings void requestSettings(); - Q_INVOKABLE void receivedSettingsReply(); // Launcher void downloadLauncher(); @@ -132,6 +140,7 @@ signals: void uiStateChanged(); void applicationStateChanged(); void downloadProgressChanged(); + void lastSignupErrorChanged(); private slots: void clientDownloadComplete(); @@ -154,8 +163,10 @@ private: // Application State ApplicationState _applicationState { ApplicationState::Init }; - LoginResponse _loginResponse; + LoginToken _loginResponse; LastLoginError _lastLoginError { NONE }; + SignupRequest::Error _lastSignupError{ SignupRequest::Error::None }; + QString _displayName; QString _applicationErrorMessage; QString _currentClientVersion; QString _buildTag { QString::null }; @@ -166,5 +177,8 @@ private: QFile _launcherZipFile; QFile _contentZipFile; + QString _username; + QString _password; + float _downloadProgress { 0 }; }; diff --git a/launchers/qt/src/LoginRequest.cpp b/launchers/qt/src/LoginRequest.cpp new file mode 100644 index 0000000000..488b5bbfa5 --- /dev/null +++ b/launchers/qt/src/LoginRequest.cpp @@ -0,0 +1,73 @@ +#include "LoginRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +void LoginRequest::send(QNetworkAccessManager& nam, QString username, QString password) { + QNetworkRequest request(QUrl(METAVERSE_API_DOMAIN + "/oauth/token")); + + request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QUrlQuery query; + query.addQueryItem("grant_type", "password"); + query.addQueryItem("username", username); + query.addQueryItem("password", password); + query.addQueryItem("scope", "owner"); + + auto reply = nam.post(request, query.toString().toUtf8()); + QObject::connect(reply, &QNetworkReply::finished, this, &LoginRequest::receivedResponse); +} + +void LoginRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + if (reply->error()) { + qDebug() << "Error logging in: " << reply->readAll(); + _error = Error::Unknown; + emit finished(); + //setApplicationState(ApplicationState::UnexpectedError); + return; + } + + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing response for login" << data; + _error = Error::BadResponse; + emit finished(); + //setApplicationStateError("Failed to login"); + return; + } + + auto root = doc.object(); + + if (!root.contains("access_token") + || !root.contains("token_type") + || !root.contains("expires_in") + || !root.contains("refresh_token") + || !root.contains("scope") + || !root.contains("created_at")) { + + _error = Error::BadUsernameOrPassword; + emit finished(); + return; + } + + _token.accessToken = root["access_token"].toString(); + _token.refreshToken = root["refresh_token"].toString(); + _token.tokenType = root["token_type"].toString(); + + qDebug() << "Got response for login: " << data; + _rawLoginToken = data; + + emit finished(); +} diff --git a/launchers/qt/src/LoginRequest.h b/launchers/qt/src/LoginRequest.h new file mode 100644 index 0000000000..07b28ff794 --- /dev/null +++ b/launchers/qt/src/LoginRequest.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +struct LoginToken { + QString accessToken; + QString tokenType; + QString refreshToken; +}; + +class LoginRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + BadResponse, + BadUsernameOrPassword + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, QString username, QString password); + Error getError() const { return _error; } + + // The token is only valid if the request has finished without error + QString getRawToken() const { return _rawLoginToken; } + LoginToken getToken() const { return _token; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; + + QString _rawLoginToken; + LoginToken _token; +}; + diff --git a/launchers/qt/src/SignupRequest.cpp b/launchers/qt/src/SignupRequest.cpp new file mode 100644 index 0000000000..a6961c0094 --- /dev/null +++ b/launchers/qt/src/SignupRequest.cpp @@ -0,0 +1,79 @@ +#include "SignupRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString username, QString password) { + if (_state != State::Unsent) { + qDebug() << "Error: Trying to send signuprequest, but not unsent"; + return; + } + + _state = State::Sending; + + QUrl signupURL { METAVERSE_API_DOMAIN }; + signupURL.setPath("/api/v1/user/channel_user"); + QNetworkRequest request(signupURL); + + request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QUrlQuery query; + query.addQueryItem("email", email); + query.addQueryItem("username", username); + query.addQueryItem("password", password); + + auto reply = nam.post(request, query.toString().toUtf8()); + QObject::connect(reply, &QNetworkReply::finished, this, &SignupRequest::receivedResponse); +} + +void SignupRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + if (reply->error() && reply->size() == 0) { + qDebug() << "Error signing up: " << reply->error() << reply->readAll(); + _error = Error::Unknown; + emit finished(); + return; + } + + auto data = reply->readAll(); + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing response for signup " << data; + _error = Error::Unknown; + emit finished(); + return; + } + + auto root = doc.object(); + + auto status = root["status"]; + if (status.isString() && status.toString() != "success") { + auto error = root["data"].toString(); + + _error = Error::Unknown; + + if (error == "no_such_email") { + _error = Error::NoSuchEmail; + } else if (error == "user_profile_already_completed") { + _error = Error::UserProfileAlreadyCompleted; + } else if (error == "bad_username") { + _error = Error::BadUsername; + } else if (error == "existing_username") { + _error = Error::ExistingUsername; + } else if (error == "bad_password") { + _error = Error::BadPassword; + } + } + + emit finished(); +} diff --git a/launchers/qt/src/SignupRequest.h b/launchers/qt/src/SignupRequest.h new file mode 100644 index 0000000000..255c0c9034 --- /dev/null +++ b/launchers/qt/src/SignupRequest.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class SignupRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + NoSuchEmail, + UserProfileAlreadyCompleted, + BadUsername, + ExistingUsername, + BadPassword, + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, QString email, QString username, QString password); + Error getError() const { return _error; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; +}; diff --git a/launchers/qt/src/UserSettingsRequest.cpp b/launchers/qt/src/UserSettingsRequest.cpp new file mode 100644 index 0000000000..62f7d5b4fb --- /dev/null +++ b/launchers/qt/src/UserSettingsRequest.cpp @@ -0,0 +1,67 @@ +#include "UserSettingsRequest.h" + +#include "Helper.h" + +#include +#include +#include +#include + +const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization"; + +void UserSettingsRequest::send(QNetworkAccessManager& nam, const LoginToken& token) { + _state = State::Sending; + + QUrl lockerURL = METAVERSE_API_DOMAIN; + lockerURL.setPath("/api/v1/user/locker"); + + QNetworkRequest lockerRequest(lockerURL); + lockerRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + lockerRequest.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent()); + lockerRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(token.accessToken).toUtf8()); + + QNetworkReply* lockerReply = nam.get(lockerRequest); + connect(lockerReply, &QNetworkReply::finished, this, &UserSettingsRequest::receivedResponse); +} + +void UserSettingsRequest::receivedResponse() { + _state = State::Finished; + + auto reply = static_cast(sender()); + + qDebug() << "Got reply: " << reply->error(); + if (reply->error()) { + _error = Error::Unknown; + emit finished(); + return; + } + + auto data = reply->readAll(); + qDebug() << "Settings: " << data; + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(data, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + qDebug() << "Error parsing settings"; + _error = Error::Unknown; + emit finished(); + return; + } + + auto root = doc.object(); + if (root["status"] != "success") { + qDebug() << "Status is not \"success\""; + _error = Error::Unknown; + emit finished(); + return; + } + + if (root["data"].toObject().contains("home_location")) { + auto homeLocation = root["data"].toObject()["home_location"]; + if (homeLocation.isString()) { + _userSettings.homeLocation = homeLocation.toString(); + } + } + + emit finished(); +} diff --git a/launchers/qt/src/UserSettingsRequest.h b/launchers/qt/src/UserSettingsRequest.h new file mode 100644 index 0000000000..5827364377 --- /dev/null +++ b/launchers/qt/src/UserSettingsRequest.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include "LoginRequest.h" + +struct UserSettings { + QString homeLocation{ QString::null }; +}; + +class UserSettingsRequest : public QObject { + Q_OBJECT +public: + enum class State { + Unsent, + Sending, + Finished + }; + + enum class Error { + None = 0, + Unknown, + }; + Q_ENUM(Error) + + void send(QNetworkAccessManager& nam, const LoginToken& token); + Error getError() const { return _error; } + + UserSettings getUserSettings() const { return _userSettings; } + +signals: + void finished(); + +private slots: + void receivedResponse(); + +private: + State _state { State::Unsent }; + Error _error { Error::None }; + + UserSettings _userSettings; +}; diff --git a/launchers/qt/src/main.cpp b/launchers/qt/src/main.cpp index df5987dd62..528e984306 100644 --- a/launchers/qt/src/main.cpp +++ b/launchers/qt/src/main.cpp @@ -1,5 +1,7 @@ #include +#include + #include "LauncherWindow.h" #include "Launcher.h" #include @@ -19,7 +21,7 @@ Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin); -bool hasSuffix(const std::string path, const std::string suffix) { +bool hasSuffix(const std::string& path, const std::string& suffix) { if (path.substr(path.find_last_of(".") + 1) == suffix) { return true; } @@ -61,6 +63,12 @@ int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setOrganizationName(name); + +#ifdef Q_OS_WIN + //QSharedMemory sharedMemory{ applicationName }; + //instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly); +#endif + Launcher launcher(argc, argv); return launcher.exec();