Update domain server settings

Move requests that require access token to go through DS
Update various styling and ease of use on DS settings page

Update domain server settings

CP

CP
This commit is contained in:
Ryan Huffman 2017-10-05 13:24:51 -07:00
parent f21a284324
commit 7dc475c695
9 changed files with 567 additions and 61 deletions

View file

@ -14,7 +14,8 @@
{
"name": "id",
"label": "Domain ID",
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank."
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank.",
"advanced": true
},
{
"name": "automatic_networking",
@ -1513,4 +1514,4 @@
]
}
]
}
}

View file

@ -1,6 +1,7 @@
body {
position: relative;
padding-bottom: 30px;
margin-top: 70px;
}
[hidden] {
@ -81,7 +82,7 @@ span.port {
#setup-sidebar.affix {
position: fixed;
top: 15px;
top: 70px;
}
#setup-sidebar button {
@ -255,3 +256,26 @@ table .headers + .headers td {
-webkit-transform: scale(1.0);
}
}
/* From https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d */
.glyphicon-refresh-animate {
-animation: spin .7s infinite linear;
-ms-animation: spin .7s infinite linear;
-webkit-animation: spinw .7s infinite linear;
-moz-animation: spinm .7s infinite linear;
}
@keyframes spin {
from { transform: scale(1) rotate(0deg);}
to { transform: scale(1) rotate(360deg);}
}
@-webkit-keyframes spinw {
from { -webkit-transform: rotate(0deg);}
to { -webkit-transform: rotate(360deg);}
}
@-moz-keyframes spinm {
from { -moz-transform: rotate(0deg);}
to { -moz-transform: rotate(360deg);}
}

View file

@ -13,7 +13,7 @@
<script src='/js/sweetalert.min.js'></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
@ -23,7 +23,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">domain-server</a>
<!--<a class="navbar-brand" href="/">domain-server</a>-->
</div>
<!-- Collect the nav links, forms, and other content for toggling -->

View file

@ -32,7 +32,6 @@ $(document).ready(function(){
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
swal( {
title: "Are you sure?",
@ -46,4 +45,4 @@ $(document).ready(function(){
});
return false;
});
});
});

View file

@ -9,7 +9,7 @@
<div class="row">
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="55" data-clampedwidth="#setup-sidebar-col">
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="1" data-clampedwidth="#setup-sidebar-col">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% panelID = group.name ? group.name : group.html_id %>
@ -26,7 +26,7 @@
</ul>
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
<button class="btn btn-success save-button">Save</button>
<button class="btn btn-success save-button" disabled>Save</button>
</div>
</div>
@ -36,6 +36,7 @@
</div>
<div class="col-xs-12">
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">

View file

@ -1,6 +1,6 @@
var Settings = {
showAdvanced: false,
METAVERSE_URL: 'https://metaverse.highfidelity.com',
METAVERSE_URL: 'https://staging.highfidelity.com',
ADVANCED_CLASS: 'advanced-setting',
DEPRECATED_CLASS: 'deprecated-setting',
TRIGGER_CHANGE_CLASS: 'trigger-change',
@ -38,6 +38,7 @@ var Settings = {
DOMAIN_ID_SELECTOR: '[name="metaverse.id"]',
ACCESS_TOKEN_SELECTOR: '[name="metaverse.access_token"]',
PLACES_TABLE_ID: 'places-table',
ADD_PLACE_BTN_ID: 'add-place-btn',
FORM_ID: 'settings-form',
INVALID_ROW_CLASS: 'invalid-input',
DATA_ROW_INDEX: 'data-row-index'
@ -469,13 +470,14 @@ function setupHFAccountButton() {
// without an access token niether of them can do anything
$("[data-keypath='metaverse.id']").hide();
$("[data-keypath='metaverse.automatic_networking']").hide();
// use the existing getFormGroup helper to ask for a button
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values);
// add the button group to the top of the metaverse panel
$('#metaverse .panel-body').prepend(buttonGroup);
}
// use the existing getFormGroup helper to ask for a button
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values);
// add the button group to the top of the metaverse panel
$('#metaverse .panel-body').prepend(buttonGroup);
}
function disonnectHighFidelityAccount() {
@ -516,6 +518,8 @@ function prepareAccessTokenPrompt() {
// we have an input value - set the access token input with this and save settings
$(Settings.ACCESS_TOKEN_SELECTOR).val(inputValue).change();
console.log("Prepping access token prompt");
// if the user doesn't have a domain ID set, give them the option to create one now
if (!Settings.data.values.metaverse.id) {
// show domain ID selection alert
@ -589,38 +593,36 @@ function showDomainCreationAlert(justConnected) {
function createNewDomainID(description, justConnected) {
// get the JSON object ready that we'll use to create a new domain
var domainJSON = {
"domain": {
"private_description": description
},
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
"private_description": description
//"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
}
$.post(Settings.METAVERSE_URL + "/api/v1/domains", domainJSON, function(data){
if (data.status == "success") {
// we successfully created a domain ID, set it on that field
var domainID = data.domain.id;
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
//$.post(Settings.METAVERSE_URL + "/api/v1/domains", domainJSON, function(data){
$.post("/api/domain", domainJSON, function(data){
// we successfully created a domain ID, set it on that field
var domainID = data.domain_id;
console.log("Setting domain id to ", data, domainID);
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
if (justConnected) {
var successText = "We connnected your High Fidelity account and created a new domain ID for this machine."
} else {
var successText = "We created a new domain ID for this machine."
}
successText += "</br></br>Click the button below to save your new settings and restart your domain-server.";
// show a sweet alert to say we are all finished up and that we need to save
swal({
title: 'Success!',
type: 'success',
text: successText,
html: true,
confirmButtonText: 'Save'
}, function(){
saveSettings();
});
if (justConnected) {
var successText = "We connnected your High Fidelity account and created a new domain ID for this machine."
} else {
var successText = "We created a new domain ID for this machine."
}
}).fail(function(){
successText += "</br></br>Click the button below to save your new settings and restart your domain-server.";
// show a sweet alert to say we are all finished up and that we need to save
swal({
title: 'Success!',
type: 'success',
text: successText,
html: true,
confirmButtonText: 'Save'
}, function(){
saveSettings();
});
}, 'json').fail(function(){
var errorText = "There was a problem creating your new domain ID. Do you want to try again or";
@ -665,6 +667,7 @@ function setupPlacesTable() {
+ " go to the <a href='" + Settings.METAVERSE_URL + "/user/places'>My Places</a> "
+ "page in your High Fidelity Metaverse account.",
read_only: true,
can_add_new_rows: false,
columns: [
{
"name": "name",
@ -672,10 +675,10 @@ function setupPlacesTable() {
},
{
"name": "path",
"label": "Path"
"label": "Viewpoint or Path"
},
{
"name": "edit",
"name": "remove",
"label": "",
"class": "buttons"
}
@ -688,6 +691,12 @@ function setupPlacesTable() {
// append the places table in the right place
$('#places_paths .panel-body').prepend(placesTableGroup);
var spinner = '<p id="loading-places-spinner" class="text-center" style="display: none">';
spinner += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span>';
spinner += '</p>';
$('#' + Settings.PLACES_TABLE_ID).after($(spinner));
// do we have a domain ID?
if (Settings.data.values.metaverse.id.length > 0) {
// now, ask the API for what places, if any, point to this domain
@ -700,22 +709,50 @@ function setupPlacesTable() {
}
function placeTableRow(name, path, isTemporary) {
function placeTableRow(name, path, isTemporary, placeID) {
var name_link = "<a href='hifi://" + name + "'>" + (isTemporary ? name + " (temporary)" : name) + "</a>";
if (isTemporary) {
var editColumn = "<td class='buttons'></td>";
} else {
var editColumn = "<td class='buttons'><a class='glyphicon glyphicon-pencil'"
+ " href='" + Settings.METAVERSE_URL + "/user/places/" + name + "/edit" + "'</a></td>";
function placeEditClicked() {
editHighFidelityPlace(placeID, name, path);
}
return "<tr><td>" + name_link + "</td><td>" + path + "</td>" + editColumn + "</tr>";
function placeDeleteClicked() {
var el = $(this);
var dialog = bootbox.confirm("Are you sure you want to remove <strong>" + name + "</strong>?", function(result) {
if (result) {
sendUpdatePlaceRequest(
placeID,
'',
true,
function() {
reloadPlacesOrTemporaryName();
dialog.modal('hide');
}, function() {
dialog.modal('hide');
});
}
return false;
});
}
if (isTemporary) {
var editLink = "";
var deleteColumn = "<td class='buttons'></td>";
} else {
var editLink = " <a class='place-edit' href='javascript:void(0);'>Edit</a>";
var deleteColumn = "<td class='buttons'><a class='place-delete glyphicon glyphicon-remove'></a></td>";
}
var row = $("<tr><td>" + name_link + "</td><td>" + path + editLink + "</td>" + deleteColumn + "</tr>");
row.find(".place-edit").click(placeEditClicked);
row.find(".place-delete").click(placeDeleteClicked);
return row;
}
function placeTableRowForPlaceObject(place) {
var placePathOrIndex = (place.path ? place.path : "/");
return placeTableRow(place.name, placePathOrIndex, false);
return placeTableRow(place.name, placePathOrIndex, false, place.id);
}
function getDomainFromAPI(callback) {
@ -729,7 +766,12 @@ function getDomainFromAPI(callback) {
}
function reloadPlacesOrTemporaryName() {
$('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
$('#loading-places-spinner').show();
getDomainFromAPI(function(data){
$('#loading-places-spinner').hide();
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
if (data.status == "success") {
if (data.domain.owner_places) {
@ -741,6 +783,18 @@ function reloadPlacesOrTemporaryName() {
// add a table row for this temporary domain name
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true));
}
var row = $("<tr> <td></td> <td></td> <td class='buttons'><a href='javascript:void(0);' class='place-add glyphicon glyphicon-plus'></a></td> </tr>");
row.find(".place-add").click(function(ev) {
chooseFromHighFidelityPlaces(null, function() {
reloadPlacesOrTemporaryName();
});
});
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(row);
} else {
}
})
}
@ -757,19 +811,208 @@ function appendDomainIDButtons() {
domainIDInput.after(createButton);
}
function showDomainSettingsModal(clickedButton) {
bootbox.dialog({
title: "Choose matching place",
message: modal_body,
buttons: modal_buttons
})
}
function sendUpdatePlaceRequest(id, path, clearDomainID, onSuccess, onFail) {
var data = {
place_id: id,
path: path
};
if (clearDomainID) {
data.domain_id = null;
}
$.ajax({
url: '/api/places',
type: 'PUT',
data: data,
success: onSuccess,
fail: onFail
});
}
function editHighFidelityPlace(placeID, name, path) {
var dialog;
var modal_body = "<div class='form-group'>";
modal_body += "<input type='text' id='place-path-input' class='form-control' value='" + path + "'>";
modal_body += "</div>";
var modal_buttons = {};
modal_buttons["success"] = {
label: 'Save',
callback: function() {
var placePath = $('#place-path-input').val();
if (path == placePath) {
return true;
}
$(this).attr('disabled', 'disabled');
sendUpdatePlaceRequest(
placeID,
placePath,
false,
function(data) {
dialog.modal('hide')
reloadPlacesOrTemporaryName();
},
function(data) {
dialog.modal('hide')
}
);
return false;
}
}
dialog = bootbox.dialog({
title: "Modify Viewpoint or Path for <strong>" + name + "</strong>",
message: modal_body,
buttons: modal_buttons
})
}
function showLoadingDialog() {
var message = '<p class="text-center">';
message += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> Loading your places...';
message += '</p>';
return bootbox.dialog({
message: message,
closeButton: false
});
}
function chooseFromHighFidelityPlaces(forcePathTo, onSuccessfullyAdded) {
if (Settings.initialValues.metaverse.access_token) {
var loadingDialog = showLoadingDialog();
$.ajax("/api/places", {
dataType: 'json',
jsonp: false,
success: function(data) {
loadingDialog.modal('hide');
console.log(data);
if (data.status == 'success') {
var modal_buttons = {
cancel: {
label: 'Cancel',
className: 'btn-default'
}
};
var dialog;
var modal_body;
if (data.data.places.length) {
// setup a select box for the returned places
modal_body = "<p>Choose the High Fidelity place to point at this domain server.</p>";
place_select = $("<select id='place-name-select' class='form-control'></select>");
_.each(data.data.places, function(place) {
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
})
modal_body += "<label for='place-name-select'>Places</label>" + place_select[0].outerHTML
if (forcePathTo === undefined || forcePathTo === null) {
modal_body += "<div class='form-group'>";
modal_body += "<label for='place-path-input' class='control-label'>Path</label>";
modal_body += "<input type='text' id='place-path-input' class='form-control' value='/'>";
modal_body += "</div>";
}
modal_buttons["success"] = {
label: 'Choose place',
callback: function() {
var placeID = $('#place-name-select').val()
// set the place ID on the form
$(Settings.place_ID_SELECTOR).val(placeID).change();
if (forcePathTo === undefined) {
var placePath = $('#place-path-input').val();
} else {
var placePath = forcePathTo;
}
$(this).attr('disabled', 'disabled');
sendUpdatePlaceRequest(
placeID,
placePath,
true,
function(data) {
dialog.modal('hide')
if (onSuccessfullyAdded) {
onSuccessfullyAdded();
}
},
function(data) {
bootbox.alert('There was an error adding this place name');
}
);
return false;
}
}
} else {
modal_buttons["success"] = {
label: 'Create new place',
callback: function() {
window.open(Settings.METAVERSE_URL + "/user/places", '_blank');
}
}
modal_body = "<p>You do not have any places in your High Fidelity account." +
"<br/><br/>Go to your <a href='https://metaverse.highfidelity.com/user/places/new'>places page</a> to create a new one. Once your place is created re-open this dialog to select it.</p>"
}
dialog = bootbox.dialog({
title: "Choose the place to point at this domain server",
message: modal_body,
buttons: modal_buttons
});
} else {
bootbox.alert("We were unable to load your place names. Please try again later.");
}
},
fail: function() {
loadingDialog.modal('hide');
bootbox.alert("Failed to retrieve your places from the High Fidelity Metaverse");
console.log("FAILURE TO GET JSON");
}
});
} else {
bootbox.alert({
message: "You must have an access token to query your High Fidelity places.<br><br>" +
"Please follow the instructions on the settings page to add an access token.",
title: "Access token required"
})
}
}
function chooseFromHighFidelityDomains(clickedButton) {
// setup the modal to help user pick their domain
if (Settings.initialValues.metaverse.access_token) {
// add a spinner to the choose button
clickedButton.html("Loading domains...")
clickedButton.attr('disabled', 'disabled')
clickedButton.html("Loading domains...");
clickedButton.attr('disabled', 'disabled');
// get a list of user domains from data-web
data_web_domains_url = Settings.METAVERSE_URL + "/api/v1/domains?access_token="
$.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
data_web_domains_url = Settings.METAVERSE_URL + "/api/v1/domains?access_token=";
data_web_domains_url += Settings.initialValues.metaverse.access_token;
data_web_domains_url = "/api/domains";
$.getJSON(data_web_domains_url, function(data){
modal_buttons = {
var modal_buttons = {
cancel: {
label: 'Cancel',
className: 'btn-default'
@ -778,8 +1021,8 @@ function chooseFromHighFidelityDomains(clickedButton) {
if (data.data.domains.length) {
// setup a select box for the returned domains
modal_body = "<p>Choose the High Fidelity domain you want this domain-server to represent.<br/>This will set your domain ID on the settings page.</p>"
domain_select = $("<select id='domain-name-select' class='form-control'></select>")
modal_body = "<p>Choose the High Fidelity domain you want this domain-server to represent.<br/>This will set your domain ID on the settings page.</p>";
domain_select = $("<select id='domain-name-select' class='form-control'></select>");
_.each(data.data.domains, function(domain){
var domainString = "";
@ -1356,6 +1599,7 @@ function getDescriptionForKey(key) {
var SAVE_BUTTON_LABEL_SAVE = "Save";
var SAVE_BUTTON_LABEL_RESTART = "Save and restart";
var reasonsForRestart = [];
var numChangesBySection = {};
function badgeSidebarForDifferences(changedElement) {
// figure out which group this input is in
@ -1405,6 +1649,20 @@ function badgeSidebarForDifferences(changedElement) {
badgeValue = ""
}
numChangesBySection[panelParentID] = badgeValue;
var hasChanges = badgeValue > 0;
if (!hasChanges) {
for (var key in numChangesBySection) {
if (numChangesBySection[key] > 0) {
hasChanges = true;
break;
}
}
}
$(".save-button").prop("disabled", !hasChanges);
$(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE);
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
}

View file

@ -46,6 +46,8 @@
#include "DomainServerNodeData.h"
#include "NodeConnectionData.h"
static const QString BASE_METAVERSE_URL = "https://staging.highfidelity.com";
int const DomainServer::EXIT_CODE_REBOOT = 234923;
#if USE_STABLE_GLOBAL_SERVICES
@ -339,6 +341,7 @@ bool DomainServer::optionallySetupOAuth() {
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
qDebug() << "OAUTH: " << _oauthProviderURL;
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
@ -1731,8 +1734,11 @@ QString DomainServer::pathForRedirect(QString path) const {
return "http://" + _hostname + ":" + QString::number(_httpManager.serverPort()) + path;
}
const QString URI_OAUTH = "/oauth";
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
qDebug() << "HTTP request received at" << connection->requestOperation() << url.toString();
const QString JSON_MIME_TYPE = "application/json";
const QString URI_ASSIGNMENT = "/assignment";
@ -1740,6 +1746,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_SETTINGS = "/settings";
const QString URI_ENTITY_FILE_UPLOAD = "/content/upload";
const QString URI_RESTART = "/restart";
const QString URI_API_PLACES = "/api/places";
const QString URI_API_DOMAIN = "/api/domain";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
@ -1899,6 +1907,56 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
connection->respond(HTTPConnection::StatusCode200);
restart();
return true;
} else if (url.path() == URI_API_DOMAIN) {
QUrl url { BASE_METAVERSE_URL + "/api/v1/domains" };
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant->isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
}
url.setQuery("access_token=" + accessTokenVariant->toString());
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = NetworkAccessManager::getInstance().get(req);
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
if (reply->error() != QNetworkReply::NoError) {
connection->respond(HTTPConnection::StatusCode500);
return;
}
static const char* CONTENT_TYPE_JSON { "application/json" };
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
});
return true;
} else if (url.path() == URI_API_PLACES) {
QUrl url { BASE_METAVERSE_URL + "/api/v1/user/places" };
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant->isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
}
url.setQuery("access_token=" + accessTokenVariant->toString());
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = NetworkAccessManager::getInstance().get(req);
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
if (reply->error() != QNetworkReply::NoError) {
connection->respond(HTTPConnection::StatusCode500);
return;
}
static const char* CONTENT_TYPE_JSON { "application/json" };
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
});
return true;
} 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);
@ -1992,8 +2050,144 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
connection->respond(HTTPConnection::StatusCode400);
}
return true;
} else if (url.path() == "/domain_settings") {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
} else if (url.path() == URI_API_DOMAIN) {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true;
}
auto params = connection->parseUrlEncodedForm();
auto it = params.find("private_description");
if (it == params.end()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
QJsonObject root {
{"access_token", accessTokenVariant->toString()},
{"domain",
QJsonObject({ { "private_description", it.value() } })
}
};
QJsonDocument doc { root };
QUrl url { BASE_METAVERSE_URL + "/api/v1/domains" };
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* reply = NetworkAccessManager::getInstance().post(req, doc.toJson());
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
auto responseData = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
connection->respond(HTTPConnection::StatusCode500,
"Error communicating with Metaverse");
return;
}
QJsonParseError error;
QJsonDocument response { QJsonDocument::fromJson(responseData, &error) };
if (error.error != QJsonParseError::NoError) {
connection->respond(HTTPConnection::StatusCode500,
"Error communicating with Metaverse, bad response");
return;
}
if (!response.isObject()) {
connection->respond(HTTPConnection::StatusCode500,
"Error communicating with Metaverse, unexpected response");
return;
}
QJsonObject root {
{ "domain_id", response.object()["domain"].toObject()["id"] },
};
static const char* CONTENT_TYPE_JSON { "application/json" };
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(root).toJson(), CONTENT_TYPE_JSON);
});
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) {
if (url.path() == URI_API_PLACES) {
auto params = connection->parseUrlEncodedForm();
auto it = params.find("place_id");
if (it == params.end()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
QString place_id = it.value();
it = params.find("path");
if (it == params.end()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
QString path = it.value();
it = params.find("domain_id");
QString domain_id;
if (it == params.end()) {
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
domain_id = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH)->toString();
} else {
domain_id = it.value();
}
QJsonObject root {
{
"place",
QJsonObject({
{ "pointee_query", domain_id },
{ "path", path }
})
}
};
QJsonDocument doc(root);
QUrl url { BASE_METAVERSE_URL + "/api/v1/places/" + place_id };
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant->isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
}
url.setQuery("access_token=" + accessTokenVariant->toString());
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* reply = NetworkAccessManager::getInstance().put(req, doc.toJson());
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Got error response from metaverse server: " << reply->readAll();
connection->respond(HTTPConnection::StatusCode500,
"Error communicating with Metaverse");
return;
}
static const char* CONTENT_TYPE_JSON { "application/json" };
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
});
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
const QString ALL_NODE_DELETE_REGEX_STRING = QString("\\%1\\/?$").arg(URI_NODES);
const QString NODE_DELETE_REGEX_STRING = QString("\\%1\\/(%2)\\/$").arg(URI_NODES).arg(UUID_REGEX_STRING);
@ -2098,6 +2292,8 @@ HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply
void DomainServer::tokenGrantFinished() {
auto tokenReply = qobject_cast<QNetworkReply*>(sender());
qDebug() << "Token grant finsihed";
if (tokenReply) {
if (tokenReply->error() == QNetworkReply::NoError) {
// now that we have a token for this profile, send off a profile request
@ -2129,6 +2325,7 @@ void DomainServer::profileRequestFinished() {
if (connection) {
if (profileReply->error() == QNetworkReply::NoError) {
qDebug() << "Reply: " << profileReply->readAll();
// call helper method to get cookieHeaders
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
@ -2298,6 +2495,8 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
profileURL.setPath("/api/v1/user/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
qDebug() << "Sending profile request to: " << profileURL;
QNetworkRequest profileRequest(profileURL);
profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);

View file

@ -17,6 +17,7 @@
#include "HTTPConnection.h"
#include "EmbeddedWebserverLogging.h"
#include "HTTPManager.h"
#include <QUrlQuery>
const char* HTTPConnection::StatusCode200 = "200 OK";
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
@ -52,10 +53,31 @@ HTTPConnection::~HTTPConnection() {
}
}
QHash<QString, QString> HTTPConnection::parseUrlEncodedForm() {
// make sure we have the correct MIME type
QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
QString contentType = elements.at(0).trimmed();
if (contentType != "application/x-www-form-urlencoded") {
return QHash<QString, QString>();
}
QUrlQuery form { _requestContent };
QHash<QString, QString> pairs;
for (auto pair : form.queryItems()) {
pairs[QUrl::fromPercentEncoding(pair.first.toLatin1())] = QUrl::fromPercentEncoding(pair.second.toLatin1());
}
return pairs;
}
QList<FormData> HTTPConnection::parseFormData() const {
// make sure we have the correct MIME type
QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
if (elements.at(0).trimmed() != "multipart/form-data") {
QString contentType = elements.at(0).trimmed();
if (contentType != "multipart/form-data") {
return QList<FormData>();
}

View file

@ -79,6 +79,8 @@ public:
/// Parses the request content as form data, returning a list of header/content pairs.
QList<FormData> parseFormData () const;
QHash<QString, QString> parseUrlEncodedForm();
/// Sends a response and closes the connection.
void respond (const char* code, const QByteArray& content = QByteArray(),
const char* contentType = DefaultContentType,