diff --git a/domain-server/resources/web/web-new/src/components/components/login/MetaverseLogin.vue b/domain-server/resources/web/web-new/src/components/components/login/MetaverseLogin.vue index b9c5bf9dfd..3fe58121d3 100644 --- a/domain-server/resources/web/web-new/src/components/components/login/MetaverseLogin.vue +++ b/domain-server/resources/web/web-new/src/components/components/login/MetaverseLogin.vue @@ -73,10 +73,10 @@ export default { methods: { async onSubmit () { - const metaverseURL = await this.retrieveMetaverseUrl(); - const result = await this.attemptLogin(metaverseURL, this.username, this.password); + const metaverseUrl = await this.retrieveMetaverseUrl(); + const result = await this.attemptLogin(metaverseUrl, this.username, this.password); - this.$emit("loginResult", { "success": result.success, "metaverse": metaverseURL, "data": result.response }); + this.$emit("loginResult", { "success": result.success, "metaverse": metaverseUrl, "data": result.response }); }, // TODO: This needs to be addressed in a more modular fashion to reuse and save state across multiple components. diff --git a/domain-server/resources/web/web-new/src/pages/FirstTimeWizard/Index.vue b/domain-server/resources/web/web-new/src/pages/FirstTimeWizard/Index.vue index 8faa567fbb..77fb061645 100644 --- a/domain-server/resources/web/web-new/src/pages/FirstTimeWizard/Index.vue +++ b/domain-server/resources/web/web-new/src/pages/FirstTimeWizard/Index.vue @@ -232,7 +232,7 @@ {{ connectMetaverseSuccess ? 'Connected' : 'Connect' }} </q-btn> <q-btn - @click="$refs.stepper.next()" + @click="connectMetaverseSuccess ? $refs.stepper.next() : mainWizardStep = 6" class="q-mb-md" :size="connectMetaverseSuccess ? 'md' : 'sm'" outline @@ -265,9 +265,58 @@ <q-step :name="5" - title="Access" + title="Configure Metaverse" caption="Recommended" :done="mainWizardStep > 5" + > + <q-card + class="wizardCard" + > + <q-card-section> + <div class="text-h6 text-weight-light text-center">Let's give your Domain a label.</div> + <div class="text-h7 text-weight-light text-center">This is to help you identify your Domains in your Metaverse account.</div> + </q-card-section> + + <q-card-section> + <q-input + v-model="domainLabel" + filled + dark + label="Label" + hint="Enter a nickname for your Domain." + lazy-rules + :rules="[ val => val && val.length > 0 || 'Please enter a label.']" + /> + </q-card-section> + + <q-card-actions vertical align="right"> + <q-btn + @click="saveMetaverseConfiguration" + class="q-mb-md" + size="md" + outline + text-color="white" + icon-right="chevron_right" + > + Next + </q-btn> + <q-btn + @click="$refs.stepper.previous()" + size="sm" + flat + icon="chevron_left" + > + Back + </q-btn> + </q-card-actions> + </q-card> + </q-step> + + <q-step + :name="6" + title="Access" + caption="Recommended" + :done="mainWizardStep > 6" > <q-card class="wizardCard" @@ -276,7 +325,7 @@ <div class="text-h6 text-weight-light text-center">Let's configure some security settings for your world.</div> </q-card-section> - <q-card-section> + <q-card-section v-show="connectMetaverseSuccess"> <div class="text-h6 text-weight-light text-center">Who should be an <b>in-world admin</b> of your Domain?</div> <q-select label="Metaverse usernames (press enter)" @@ -323,7 +372,7 @@ Next </q-btn> <q-btn - @click="$refs.stepper.previous()" + @click="connectMetaverseSuccess ? $refs.stepper.previous() : mainWizardStep = 4" size="sm" flat icon="chevron_left" @@ -335,10 +384,10 @@ </q-step> <q-step - :name="6" + :name="7" title="Administrator" caption="Highly Recommended" - :done="mainWizardStep > 6" + :done="mainWizardStep > 7" > <q-card class="wizardCard" @@ -444,10 +493,10 @@ </q-step> <q-step - :name="7" + :name="8" title="Performance" caption="" - :done="mainWizardStep > 7" + :done="mainWizardStep > 8" > <q-card class="wizardCard" @@ -492,10 +541,10 @@ </q-step> <q-step - :name="8" + :name="9" title="Done!" caption="" - :done="mainWizardStep > 8" + :done="mainWizardStep > 9" > <q-card class="wizardCard" @@ -615,6 +664,8 @@ export default defineComponent({ // TODO: Needs to be based off of the actual state of the server's connection (using Vuex and retrieving settings on page load.) connectMetaverseSuccess: false, connectMetaverseDialog: false, + // Metaverse Configuration + domainLabel: "", // Security Step administratorsListSecurityModel: [], connectionSecurityModel: ref(["everyone"]), @@ -658,10 +709,10 @@ export default defineComponent({ // Starter Content starterContentToggle: true, // Consts - WELCOME_TEXT_TIMEOUT: 4500, + WELCOME_TEXT_TIMEOUT: 500, MAIN_WIZARD_TRANSITION_TIME: 1000, DEFAULT_METAVERSE_URL: "https://metaverse.vircadia.com/live", - FINAL_WIZARD_STEP: 8, + FINAL_WIZARD_STEP: 9, COMPLETE_WIZARD_REDIRECT_DELAY: 4000 }; }, @@ -686,6 +737,67 @@ export default defineComponent({ } }, + saveMetaverseConfiguration () { + this.mainWizardStep++; + // TODO: Put this path in a constant somewhere. + axios.post(`/api/domains`, { "domain": { "label": this.domainLabel } }, + { + params: { + label: this.domainLabel + }, + headers: { + 'content-type': 'application/x-www-form-urlencoded;charset=utf-8' + } + } + ) + .then((response) => { + Log.info(Log.types.METAVERSE, "Successfully configured Domain with Metaverse."); + console.info("received", response); + const settingsToCommit = { + "metaverse": { + "automatic_networking": "full", + "id": response.data.domain.domainId + }, + "descriptors": { + "world_name": this.domainLabel + } + }; + + this.commitMetaverseConfig(settingsToCommit); + }) + .catch((error) => { + Log.error(Log.types.METAVERSE, `Failed to configure Domain with Metaverse: ${error}`); + this.$q.notify({ + type: "negative", + textColor: "white", + icon: "warning", + message: `Failed to label your Domain on the Metaverse: ${error}` + }); + }); + }, + + async commitMetaverseConfig (jsonToCommit) { + const committed = await this.commitSettings(jsonToCommit); + + if (committed === true) { + Log.info(Log.types.METAVERSE, "Successfully committed Domain server config for the Metaverse."); + this.$q.notify({ + type: "positive", + textColor: "white", + icon: "cloud_done", + message: "Successfully labeled your Domain on your Metaverse account." + }); + } else { + Log.error(Log.types.METAVERSE, "Failed to configure server with Metaverse: Could not commit config to settings."); + this.$q.notify({ + type: "negative", + textColor: "white", + icon: "warning", + message: "Domain label with Metaverse attempt failed because the settings were unable to be saved." + }); + } + }, + async saveSecuritySettings () { const friendsCanConnect = this.connectionSecurityModel.includes("friends"); const friendsCanRez = this.rezSecurityModel.includes("friends"); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4a4bc2fe33..a3e0879930 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -87,6 +87,7 @@ QString DomainServer::_userConfigFilename; int DomainServer::_parentPID { -1 }; bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, + const QUrl& requestUrl, const QString& metaversePath, const QString& requestSubobjectKey, std::initializer_list<QString> requiredData, @@ -101,23 +102,43 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, QJsonObject subobject; - auto params = connection->parseUrlEncodedForm(); + if (requestUrl.hasQuery()) { + QUrlQuery query(requestUrl); - for (auto& key : requiredData) { - auto it = params.find(key); - if (it == params.end()) { - auto error = "Bad request, expected param '" + key + "'"; - connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); - return true; + for (auto& key : requiredData) { + if (query.hasQueryItem(key)) { + subobject.insert(key, query.queryItemValue(key)); + } else { + auto error = "Domain Server: Bad request, expected param '" + key + "'"; + connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); + return true; + } } - subobject.insert(key, it.value()); - } - for (auto& key : optionalData) { - auto it = params.find(key); - if (it != params.end()) { + for (auto& key : optionalData) { + if (query.hasQueryItem(key)) { + subobject.insert(key, query.queryItemValue(key)); + } + } + } else { + auto params = connection->parseUrlEncodedForm(); + + for (auto& key : requiredData) { + auto it = params.find(key); + if (it == params.end()) { + auto error = "Domain Server: Bad request, expected param '" + key + "'"; + connection->respond(HTTPConnection::StatusCode400, error.toLatin1()); + return true; + } subobject.insert(key, it.value()); } + + for (auto& key : optionalData) { + auto it = params.find(key); + if (it != params.end()) { + subobject.insert(key, it.value()); + } + } } QJsonObject root; @@ -2296,12 +2317,12 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_API_DOMAINS) { - return forwardMetaverseAPIRequest(connection, "/api/v1/domains", ""); + return forwardMetaverseAPIRequest(connection, url, "/api/v1/domains", ""); } else if (url.path().startsWith(URI_API_DOMAINS_ID)) { auto id = url.path().mid(URI_API_DOMAINS_ID.length()); - return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + id, "", {}, {}, false); + return forwardMetaverseAPIRequest(connection, url, "/api/v1/domains/" + id, "", {}, {}, false); } else if (url.path() == URI_API_PLACES) { - return forwardMetaverseAPIRequest(connection, "/api/v1/user/places", ""); + return forwardMetaverseAPIRequest(connection, url, "/api/v1/user/places", ""); } else { // check if this is for json stats for a node const QString NODE_JSON_REGEX_STRING = QString("\\%1\\/(%2).json\\/?$").arg(URI_NODES).arg(UUID_REGEX_STRING); @@ -2438,7 +2459,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } } else if (url.path() == URI_API_DOMAINS) { - return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "domain", { "label" }); + return forwardMetaverseAPIRequest(connection, url, "/api/v1/domains", "domain", { "label" }); } else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) { auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length()); @@ -2465,7 +2486,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } auto domainID = domainSetting.toString(); - return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain", + return forwardMetaverseAPIRequest(connection, url, "/api/v1/domains/" + domainID, "domain", { }, { "network_address", "network_port", "label" }); } else if (url.path() == URI_API_PLACES) { auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index deb204de1c..d0b350fb7a 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -228,6 +228,7 @@ private: bool processPendingContent(HTTPConnection* connection, QString itemName, QString filename, QByteArray dataChunk); bool forwardMetaverseAPIRequest(HTTPConnection* connection, + const QUrl& requestUrl, const QString& metaversePath, const QString& requestSubobject, std::initializer_list<QString> requiredData = { }, diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 5932c7ed56..f5f0fe0289 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -176,7 +176,7 @@ QList<FormData> HTTPConnection::parseFormData() const { break; } } - + QByteArray start = "--" + boundary; QByteArray end = "\r\n--" + boundary + "--\r\n"; @@ -394,6 +394,6 @@ void HTTPConnection::readContent() { if (_requestContent->bytesLeftToWrite() == 0) { _socket->disconnect(this, SLOT(readContent())); - _parentManager->handleHTTPRequest(this, _requestUrl.path()); + _parentManager->handleHTTPRequest(this, _requestUrl); } }