mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-14 10:56:12 +02:00
Merge remote-tracking branch 'upstream/master' into character_entity_fixes
this is just to synch this branch with the upstream master
This commit is contained in:
commit
3ce65ece87
175 changed files with 5042 additions and 2065 deletions
|
@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
|||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"};
|
||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
|
||||
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"};
|
||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
|
||||
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
||||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <EntityEditFilters.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <AddressManager.h>
|
||||
|
||||
#include "AssignmentParentFinder.h"
|
||||
#include "EntityNodeData.h"
|
||||
|
@ -29,15 +33,26 @@ const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
|
|||
|
||||
EntityServer::EntityServer(ReceivedMessage& message) :
|
||||
OctreeServer(message),
|
||||
_entitySimulation(NULL)
|
||||
_entitySimulation(NULL),
|
||||
_dynamicDomainVerificationTimer(this)
|
||||
{
|
||||
DependencyManager::set<ResourceManager>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics },
|
||||
this, "handleEntityPacket");
|
||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||
PacketType::EntityEdit,
|
||||
PacketType::EntityErase,
|
||||
PacketType::EntityPhysics,
|
||||
PacketType::ChallengeOwnership,
|
||||
PacketType::ChallengeOwnershipRequest,
|
||||
PacketType::ChallengeOwnershipReply },
|
||||
this,
|
||||
"handleEntityPacket");
|
||||
|
||||
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
|
||||
_dynamicDomainVerificationTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
EntityServer::~EntityServer() {
|
||||
|
@ -93,6 +108,9 @@ void EntityServer::beforeRun() {
|
|||
connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
|
||||
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
|
||||
_pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
|
||||
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &EntityServer::domainSettingsRequestFailed);
|
||||
}
|
||||
|
||||
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
|
||||
|
@ -296,6 +314,18 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME);
|
||||
}
|
||||
|
||||
int minTime;
|
||||
if (readOptionInt("dynamicDomainVerificationTimeMin", settingsSectionObject, minTime)) {
|
||||
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = minTime * 1000;
|
||||
}
|
||||
|
||||
int maxTime;
|
||||
if (readOptionInt("dynamicDomainVerificationTimeMax", settingsSectionObject, maxTime)) {
|
||||
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = maxTime * 1000;
|
||||
}
|
||||
|
||||
startDynamicDomainVerification();
|
||||
|
||||
tree->setWantEditLogging(wantEditLogging);
|
||||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||
|
||||
|
@ -410,3 +440,79 @@ QString EntityServer::serverSubclassStats() {
|
|||
|
||||
return statsString;
|
||||
}
|
||||
|
||||
void EntityServer::domainSettingsRequestFailed() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
qCDebug(entities) << "The EntityServer couldn't get the Domain Settings. Starting dynamic domain verification with default values...";
|
||||
|
||||
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
|
||||
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
|
||||
startDynamicDomainVerification();
|
||||
}
|
||||
|
||||
void EntityServer::startDynamicDomainVerification() {
|
||||
qCDebug(entities) << "Starting Dynamic Domain Verification...";
|
||||
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
|
||||
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
|
||||
|
||||
QHashIterator<QString, EntityItemID> i(localMap);
|
||||
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
|
||||
|
||||
if (entity) {
|
||||
if (!entity->getProperties().verifyStaticCertificateProperties()) {
|
||||
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
|
||||
<< "static certificate verification.";
|
||||
// Delete the entity if it doesn't pass static certificate verification
|
||||
tree->deleteEntity(i.value(), true);
|
||||
} else {
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = i.key();
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (jsonObject["domain_id"].toString() != thisDomainID) {
|
||||
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
|
||||
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
|
||||
tree->deleteEntity(i.value(), true);
|
||||
} else {
|
||||
qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value()
|
||||
<< "More info:" << jsonObject;
|
||||
tree->deleteEntity(i.value(), true);
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!";
|
||||
}
|
||||
}
|
||||
|
||||
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
|
||||
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";
|
||||
_dynamicDomainVerificationTimer.start(nextInterval);
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ protected:
|
|||
|
||||
private slots:
|
||||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void domainSettingsRequestFailed();
|
||||
|
||||
private:
|
||||
SimpleEntitySimulationPointer _entitySimulation;
|
||||
|
@ -80,6 +81,13 @@ private:
|
|||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||
|
||||
static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m
|
||||
static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h
|
||||
int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m
|
||||
int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h
|
||||
QTimer _dynamicDomainVerificationTimer;
|
||||
void startDynamicDomainVerification();
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -175,7 +175,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte
|
|||
return parentWasNew || ancestorsWereNew;
|
||||
}
|
||||
|
||||
// since we didn't have a parent niether of our parents or ancestors could be new additions
|
||||
// since we didn't have a parent, neither of our parents or ancestors could be new additions
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
|
|||
return hasNewChild || hasNewDescendants;
|
||||
}
|
||||
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum) {
|
||||
|
||||
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
|
@ -423,12 +425,19 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
uint64_t sendTime = usecTimestampNow();
|
||||
auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
|
||||
nodeData->stats.encodeStarted();
|
||||
auto entityNode = _node.toStrongRef();
|
||||
auto entityNodeData = static_cast<EntityNodeData*>(entityNode->getLinkedData());
|
||||
while(!_sendQueue.empty()) {
|
||||
PrioritizedEntity queuedItem = _sendQueue.top();
|
||||
EntityItemPointer entity = queuedItem.getEntity();
|
||||
if (entity) {
|
||||
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
|
||||
if (entity->matchesJSONFilters(jsonFilters)) {
|
||||
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
|
||||
if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
|
||||
if (!jsonFilters.isEmpty() && entityMatchesFilters) {
|
||||
// Record explicitly filtered-in entity so that extra entities can be flagged.
|
||||
entityNodeData->insertSentFilteredEntity(entity->getID());
|
||||
}
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
|
||||
|
||||
if (appendEntityState != OctreeElement::COMPLETED) {
|
||||
|
|
|
@ -38,7 +38,8 @@ private:
|
|||
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
|
||||
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
|
||||
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
|
||||
|
||||
void preDistributionProcessing() override;
|
||||
|
|
|
@ -92,7 +92,19 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
|||
// Ask our tree subclass if it can handle the incoming packet...
|
||||
PacketType packetType = message->getType();
|
||||
|
||||
if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
||||
if (packetType == PacketType::ChallengeOwnership) {
|
||||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (packetType == PacketType::ChallengeOwnershipRequest) {
|
||||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (packetType == PacketType::ChallengeOwnershipReply) {
|
||||
_myServer->getOctree()->withWriteLock([&] {
|
||||
_myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode);
|
||||
});
|
||||
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
||||
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
|
||||
_receivedPacketCount++;
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#include <HTTPConnection.h>
|
||||
#include <LogHandler.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <UUID.h>
|
||||
|
||||
|
|
11
cmake/externals/draco/CMakeLists.txt
vendored
11
cmake/externals/draco/CMakeLists.txt
vendored
|
@ -13,7 +13,7 @@ ExternalProject_Add(
|
|||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/draco-1.1.0.zip
|
||||
URL_MD5 208f8b04c91d5f1c73d731a3ea37c5bb
|
||||
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTRA_CMAKE_FLAGS}
|
||||
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>-$<CONFIG> ${EXTRA_CMAKE_FLAGS}
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
|
@ -23,10 +23,11 @@ ExternalProject_Add(
|
|||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$<CONFIG>")
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of Draco include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include CACHE PATH "List of Draco include directories")
|
||||
|
||||
if (UNIX)
|
||||
set(LIB_PREFIX "lib")
|
||||
|
@ -35,6 +36,6 @@ elseif (WIN32)
|
|||
set(LIB_EXT "lib")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}draco.${LIB_EXT} CACHE FILEPATH "Path to Draco release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_ENCODER_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}dracoenc.${LIB_EXT} CACHE FILEPATH "Path to Draco encoder release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_DECODER_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}dracodec.${LIB_EXT} CACHE FILEPATH "Path to Draco decoder release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}draco.${LIB_EXT} CACHE FILEPATH "Path to Draco release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_ENCODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracoenc.${LIB_EXT} CACHE FILEPATH "Path to Draco encoder release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_DECODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracodec.${LIB_EXT} CACHE FILEPATH "Path to Draco decoder release library")
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
|
||||
URL_MD5 94f4765bdbcd53cd099f349ae031e769
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
|
||||
URL_MD5 4f40e49715a420fb67b45b9cee19052c
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN)
|
|||
select_library_configurations(LIB_EAY)
|
||||
select_library_configurations(SSL_EAY)
|
||||
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
|
||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
|
||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH)
|
||||
endif()
|
||||
else()
|
||||
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
{
|
||||
"version": 1.8,
|
||||
"version": 2.0,
|
||||
"settings": [
|
||||
{
|
||||
"name": "label",
|
||||
"label": "Label",
|
||||
"settings": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse / Networking",
|
||||
|
@ -14,7 +20,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",
|
||||
|
@ -82,11 +89,13 @@
|
|||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"advanced": true,
|
||||
"help": "A description of your domain (256 character limit)."
|
||||
},
|
||||
{
|
||||
"name": "maturity",
|
||||
"label": "Maturity",
|
||||
"advanced": true,
|
||||
"help": "A maturity rating, available as a guideline for content on your domain.",
|
||||
"default": "unrated",
|
||||
"type": "select",
|
||||
|
@ -116,6 +125,7 @@
|
|||
{
|
||||
"name": "hosts",
|
||||
"label": "Hosts",
|
||||
"advanced": true,
|
||||
"type": "table",
|
||||
"can_add_new_rows": true,
|
||||
"help": "Usernames of hosts who can reliably show your domain to new visitors.",
|
||||
|
@ -131,6 +141,7 @@
|
|||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"advanced": true,
|
||||
"type": "table",
|
||||
"can_add_new_rows": true,
|
||||
"help": "Common categories under which your domain falls.",
|
||||
|
@ -207,7 +218,7 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
"groups": [
|
||||
|
@ -216,8 +227,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -253,6 +264,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -283,7 +308,7 @@
|
|||
}
|
||||
],
|
||||
"non-deletable-row-key": "permissions_id",
|
||||
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||
"non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ]
|
||||
},
|
||||
{
|
||||
"name": "group_permissions",
|
||||
|
@ -300,8 +325,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -362,6 +387,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -383,7 +422,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -407,8 +446,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -466,6 +505,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -487,7 +540,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -507,8 +560,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -544,6 +597,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -565,7 +632,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -585,8 +652,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -622,6 +689,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -643,7 +724,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -663,8 +744,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -700,6 +781,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -721,7 +816,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -741,8 +836,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 8
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 10
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -778,6 +873,20 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_certified",
|
||||
"label": "Rez Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp_certified",
|
||||
"label": "Rez Temporary Certified",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
|
@ -799,7 +908,7 @@
|
|||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "id_can_replace_content",
|
||||
"label": "Replace Content",
|
||||
"type": "checkbox",
|
||||
|
@ -841,7 +950,7 @@
|
|||
{
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server (ATP)",
|
||||
"assignment-types": [3],
|
||||
"assignment-types": [ 3 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "enabled",
|
||||
|
@ -872,7 +981,7 @@
|
|||
{
|
||||
"name": "entity_script_server",
|
||||
"label": "Entity Script Server (ESS)",
|
||||
"assignment-types": [5],
|
||||
"assignment-types": [ 5 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "entity_pps_per_script",
|
||||
|
@ -895,7 +1004,7 @@
|
|||
{
|
||||
"name": "avatars",
|
||||
"label": "Avatars",
|
||||
"assignment-types": [1, 2],
|
||||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_scale",
|
||||
|
@ -934,7 +1043,7 @@
|
|||
{
|
||||
"name": "audio_threading",
|
||||
"label": "Audio Threading",
|
||||
"assignment-types": [0],
|
||||
"assignment-types": [ 0 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "auto_threads",
|
||||
|
@ -957,7 +1066,7 @@
|
|||
{
|
||||
"name": "audio_env",
|
||||
"label": "Audio Environment",
|
||||
"assignment-types": [0],
|
||||
"assignment-types": [ 0 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "attenuation_per_doubling_in_distance",
|
||||
|
@ -1164,6 +1273,22 @@
|
|||
"default": "3600",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "dynamicDomainVerificationTimeMin",
|
||||
"label": "Dynamic Domain Verification Time (seconds) - Minimum",
|
||||
"help": "The lower limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
|
||||
"placeholder": "2700",
|
||||
"default": "2700",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "dynamicDomainVerificationTimeMax",
|
||||
"label": "Dynamic Domain Verification Time (seconds) - Maximum",
|
||||
"help": "The upper limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
|
||||
"placeholder": "3600",
|
||||
"default": "3600",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "entityScriptSourceWhitelist",
|
||||
"label": "Entity Scripts Allowed from:",
|
||||
|
@ -1511,6 +1636,29 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "wizard",
|
||||
"label": "Setup Wizard",
|
||||
"restart": false,
|
||||
"hidden": true,
|
||||
"settings": [
|
||||
{
|
||||
"name": "cloud_domain",
|
||||
"type": "checkbox",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "steps_completed",
|
||||
"type": "int",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"name": "completed_once",
|
||||
"type": "checkbox",
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
||||
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
||||
</p>
|
||||
<p>
|
||||
If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
</p>
|
||||
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
|
||||
<label class="control-label">Windows</label>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">OSX</label>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">Linux</label>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<br>
|
||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||
<br>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
body {
|
||||
position: relative;
|
||||
padding-bottom: 30px;
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
|
@ -27,14 +28,14 @@ body {
|
|||
.table .value-row td,
|
||||
.table .value-category td,
|
||||
.table .inputs td {
|
||||
vertical-align: middle;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table .table-checkbox {
|
||||
/* Fix IE sizing checkboxes to fill table cell */
|
||||
width: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* Fix IE sizing checkboxes to fill table cell */
|
||||
width: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.value-category:not(.inputs) {
|
||||
|
@ -80,8 +81,10 @@ span.port {
|
|||
}
|
||||
|
||||
#setup-sidebar.affix {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
/* This overrides a case where going to the bottom of the page,
|
||||
* then scrolling up, causes `position: relative` to be added to the style
|
||||
*/
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
#setup-sidebar button {
|
||||
|
@ -145,55 +148,55 @@ table {
|
|||
}
|
||||
|
||||
caption {
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
padding-top: 0;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
table > tbody > .headers > td {
|
||||
vertical-align: middle;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table .headers + .headers td {
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
font-size: 13px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
#security table .headers td + td {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tooltip.top .tooltip-arrow {
|
||||
border-top-color: #fff;
|
||||
border-width: 10px 10px 0;
|
||||
margin-bottom: -5px;
|
||||
border-top-color: #fff;
|
||||
border-width: 10px 10px 0;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
padding: 20px 20px 10px 20px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 8px 8px #e8e8e8;
|
||||
padding: 20px 20px 10px 20px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 8px 8px #e8e8e8;
|
||||
}
|
||||
|
||||
.tooltip.in {
|
||||
opacity: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltip-inner ul {
|
||||
padding-left: 0;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tooltip-inner li {
|
||||
list-style-type: none;
|
||||
margin-bottom: 5px;
|
||||
list-style-type: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#security .tooltip-inner {
|
||||
max-width: 520px;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
#xs-advanced-container {
|
||||
|
@ -241,6 +244,20 @@ table .headers + .headers td {
|
|||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.col-centered {
|
||||
float: none;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.centered-hack-parent {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.centered-hack {
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bouncedelay {
|
||||
0%, 80%, 100% { -webkit-transform: scale(0.0) }
|
||||
40% { -webkit-transform: scale(1.0) }
|
||||
|
@ -255,3 +272,50 @@ 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); }
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
padding-top: 10px;
|
||||
color: #EB5757;
|
||||
}
|
||||
|
||||
.account-connected-header {
|
||||
color: #6FCF97;
|
||||
font-size: 30px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#visit-domain-link,
|
||||
.blue-link {
|
||||
font-size: 14px;
|
||||
text-decoration-line: underline;
|
||||
font-weight: normal;
|
||||
color: #2F80ED;
|
||||
}
|
||||
|
||||
#manage-cloud-domains-link {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
BIN
domain-server/resources/web/favicon.ico
Normal file
BIN
domain-server/resources/web/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -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,6 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">domain-server</a>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
|
@ -40,6 +39,7 @@
|
|||
<li><a href="/settings/">Settings</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-right navbar-nav">
|
||||
<li><a id="visit-domain-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
||||
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
25
domain-server/resources/web/images/checkmark.svg
Normal file
25
domain-server/resources/web/images/checkmark.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<svg width="676" height="676" viewBox="0 0 676 676" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>CongratulationImage</title>
|
||||
<desc>Created using Figma</desc>
|
||||
<g id="Canvas" transform="matrix(4 0 0 4 -21208 -17980)">
|
||||
<g id="CongratulationImage">
|
||||
<g id="Ellipse">
|
||||
<use xlink:href="#path0_fill" transform="translate(5302 4495)" fill="#FFFFFF"/>
|
||||
<mask id="mask0_outline_ins">
|
||||
<use xlink:href="#path0_fill" fill="white" transform="translate(5302 4495)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_outline_ins)">
|
||||
<use xlink:href="#path1_stroke_2x" transform="translate(5302 4495)" fill="#219653"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Vector 2">
|
||||
<use xlink:href="#path2_stroke" transform="translate(5355 4559)" fill="#219653"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<path id="path0_fill" d="M 169 84.5C 169 131.168 131.168 169 84.5 169C 37.8319 169 0 131.168 0 84.5C 0 37.8319 37.8319 0 84.5 0C 131.168 0 169 37.8319 169 84.5Z"/>
|
||||
<path id="path1_stroke_2x" d="M 154 84.5C 154 122.884 122.884 154 84.5 154L 84.5 184C 139.452 184 184 139.452 184 84.5L 154 84.5ZM 84.5 154C 46.1162 154 15 122.884 15 84.5L -15 84.5C -15 139.452 29.5477 184 84.5 184L 84.5 154ZM 15 84.5C 15 46.1162 46.1162 15 84.5 15L 84.5 -15C 29.5477 -15 -15 29.5477 -15 84.5L 15 84.5ZM 84.5 15C 122.884 15 154 46.1162 154 84.5L 184 84.5C 184 29.5477 139.452 -15 84.5 -15L 84.5 15Z"/>
|
||||
<path id="path2_stroke" d="M 5.18747 19.8031C 2.19593 16.9382 -2.5517 17.0408 -5.41666 20.0323C -8.28162 23.0238 -8.17901 27.7715 -5.18747 30.6364L 5.18747 19.8031ZM 20.6541 45L 15.4667 50.4167C 18.3816 53.2083 22.9831 53.1924 25.8787 50.3809L 20.6541 45ZM 72.2246 5.38085C 75.1964 2.49539 75.2663 -2.25283 72.3809 -5.2246C 69.4954 -8.19636 64.7472 -8.26632 61.7754 -5.38085L 72.2246 5.38085ZM -5.18747 30.6364L 15.4667 50.4167L 25.8416 39.5833L 5.18747 19.8031L -5.18747 30.6364ZM 25.8787 50.3809L 72.2246 5.38085L 61.7754 -5.38085L 15.4295 39.6191L 25.8787 50.3809Z"/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
462
domain-server/resources/web/js/shared.js
Normal file
462
domain-server/resources/web/js/shared.js
Normal file
|
@ -0,0 +1,462 @@
|
|||
var Settings = {
|
||||
showAdvanced: false,
|
||||
ADVANCED_CLASS: 'advanced-setting',
|
||||
DEPRECATED_CLASS: 'deprecated-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
DATA_CATEGORY_CLASS: 'value-category',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: 'del-row',
|
||||
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
|
||||
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
|
||||
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
|
||||
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
|
||||
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
|
||||
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
|
||||
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
|
||||
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
|
||||
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
|
||||
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
|
||||
MOVE_UP_BUTTON_CLASS: 'move-up',
|
||||
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
MOVE_DOWN_BUTTON_CLASS: 'move-down',
|
||||
MOVE_DOWN_SPAN_CLASSES: 'glyphicon glyphicon-chevron-down move-down',
|
||||
TABLE_BUTTONS_CLASS: 'buttons',
|
||||
ADD_DEL_BUTTONS_CLASS: 'add-del-buttons',
|
||||
ADD_DEL_BUTTONS_CLASSES: 'buttons add-del-buttons',
|
||||
REORDER_BUTTONS_CLASS: 'reorder-buttons',
|
||||
REORDER_BUTTONS_CLASSES: 'buttons reorder-buttons',
|
||||
NEW_ROW_CLASS: 'new-row',
|
||||
CONNECT_ACCOUNT_BTN_ID: 'connect-account-btn',
|
||||
DISCONNECT_ACCOUNT_BTN_ID: 'disconnect-account-btn',
|
||||
CREATE_DOMAIN_ID_BTN_ID: 'create-domain-btn',
|
||||
CHOOSE_DOMAIN_ID_BTN_ID: 'choose-domain-btn',
|
||||
GET_TEMPORARY_NAME_BTN_ID: 'get-temp-name-btn',
|
||||
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'
|
||||
};
|
||||
|
||||
var URLs = {
|
||||
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
|
||||
// STAGING METAVERSE_URL: https://staging.highfidelity.com
|
||||
METAVERSE_URL: 'https://metaverse.highfidelity.com',
|
||||
PLACE_URL: 'https://hifi.place',
|
||||
};
|
||||
|
||||
var Strings = {
|
||||
LOADING_SETTINGS_ERROR: "There was a problem loading the domain settings.\nPlease refresh the page to try again.",
|
||||
|
||||
CHOOSE_DOMAIN_BUTTON: "Choose from my domains",
|
||||
CREATE_DOMAIN_BUTTON: "Create new domain ID",
|
||||
CREATE_DOMAIN_SUCCESS_JUST_CONNECTED: "We connnected your High Fidelity account and created a new domain ID for this machine.",
|
||||
CREATE_DOMAIN_SUCCESS: "We created a new domain ID for this machine.",
|
||||
|
||||
// When a place modification fails, they will be brought back to the previous
|
||||
// dialog with new path still set, allowing them to retry immediately, and without
|
||||
// having to type the new path in again.
|
||||
EDIT_PLACE_TITLE: "Modify Viewpoint or Path",
|
||||
EDIT_PLACE_ERROR: "Failed to update place path. Please try again.",
|
||||
EDIT_PLACE_CONFIRM_BUTTON: "Save",
|
||||
EDIT_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
|
||||
EDIT_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong>?",
|
||||
REMOVE_PLACE_ERROR: "Failed to remove place. Please try again.",
|
||||
REMOVE_PLACE_DELETE_BUTTON: "Delete",
|
||||
REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...",
|
||||
REMOVE_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
|
||||
ADD_PLACE_TITLE: "Choose a place",
|
||||
ADD_PLACE_MESSAGE: "Choose the High Fidelity place to point at this domain server.",
|
||||
ADD_PLACE_CONFIRM_BUTTON: "Choose place",
|
||||
ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
|
||||
ADD_PLACE_CANCEL_BUTTON: "Cancel",
|
||||
ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this place name.",
|
||||
|
||||
ADD_PLACE_NO_PLACES_MESSAGE: "<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>",
|
||||
ADD_PLACE_NO_PLACES_BUTTON: "Create new place",
|
||||
ADD_PLACE_UNABLE_TO_LOAD_ERROR: "We were unable to load your place names. Please try again later.",
|
||||
ADD_PLACE_LOADING_DIALOG: "Loading your places...",
|
||||
|
||||
ADD_PLACE_NOT_CONNECTED_TITLE: "Access token required",
|
||||
ADD_PLACE_NOT_CONNECTED_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.",
|
||||
};
|
||||
|
||||
var DOMAIN_ID_TYPE_NONE = 0;
|
||||
var DOMAIN_ID_TYPE_TEMP = 1;
|
||||
var DOMAIN_ID_TYPE_FULL = 2;
|
||||
var DOMAIN_ID_TYPE_UNKNOWN = 3;
|
||||
|
||||
function domainIDIsSet() {
|
||||
return Settings.data.values.metaverse.id.length > 0;
|
||||
}
|
||||
|
||||
function getCurrentDomainIDType() {
|
||||
if (!domainIDIsSet()) {
|
||||
return DOMAIN_ID_TYPE_NONE;
|
||||
}
|
||||
if (typeof DomainInfo === 'undefined') {
|
||||
return DOMAIN_ID_TYPE_UNKNOWN;
|
||||
}
|
||||
if (DomainInfo !== null) {
|
||||
if (DomainInfo.name !== undefined) {
|
||||
return DOMAIN_ID_TYPE_TEMP;
|
||||
}
|
||||
return DOMAIN_ID_TYPE_FULL;
|
||||
}
|
||||
return DOMAIN_ID_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
function showLoadingDialog(msg) {
|
||||
var message = '<div class="text-center">';
|
||||
message += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> ' + msg;
|
||||
message += '</div>';
|
||||
|
||||
return bootbox.dialog({
|
||||
message: message,
|
||||
closeButton: false
|
||||
});
|
||||
}
|
||||
|
||||
function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, onError) {
|
||||
var data = {
|
||||
place_id: id,
|
||||
path: path
|
||||
};
|
||||
if (domainID) {
|
||||
data.domain_id = domainID;
|
||||
}
|
||||
if (clearDomainID) {
|
||||
data.domain_id = null;
|
||||
}
|
||||
$.ajax({
|
||||
url: '/api/places',
|
||||
type: 'PUT',
|
||||
data: data,
|
||||
success: onSuccess,
|
||||
error: onError
|
||||
});
|
||||
}
|
||||
|
||||
var pendingDomainRequest = null;
|
||||
function getDomainFromAPI(callback) {
|
||||
if (pendingDomainRequest !== null) {
|
||||
pendingDomainRequest.success(callback);
|
||||
pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
|
||||
return pendingDomainRequest;
|
||||
}
|
||||
|
||||
if (callback === undefined) {
|
||||
callback = function() {};
|
||||
}
|
||||
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
if (domainID === null || domainID === undefined || domainID === '') {
|
||||
callback({ status: 'fail' });
|
||||
return null;
|
||||
}
|
||||
|
||||
pendingDomainRequest = $.ajax({
|
||||
url: "/api/domains/" + domainID,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
pendingDomainRequest = null;
|
||||
|
||||
if (data.status === 'success') {
|
||||
DomainInfo = data.domain;
|
||||
} else {
|
||||
DomainInfo = null;
|
||||
}
|
||||
callback(data);
|
||||
},
|
||||
error: function() {
|
||||
pendingDomainRequest = null;
|
||||
|
||||
DomainInfo = null;
|
||||
callback({ status: 'fail' });
|
||||
}
|
||||
});
|
||||
|
||||
return pendingDomainRequest;
|
||||
}
|
||||
|
||||
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
|
||||
if (accessToken) {
|
||||
|
||||
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
|
||||
|
||||
function loadPlaces() {
|
||||
$.ajax("/api/places", {
|
||||
dataType: 'json',
|
||||
jsonp: false,
|
||||
success: function(data) {
|
||||
if (data.status == 'success') {
|
||||
var modal_buttons = {
|
||||
cancel: {
|
||||
label: Strings.ADD_PLACE_CANCEL_BUTTON,
|
||||
className: 'add-place-cancel-button btn-default'
|
||||
}
|
||||
};
|
||||
|
||||
var dialog;
|
||||
var modal_body;
|
||||
|
||||
if (data.data.places.length) {
|
||||
var places_by_id = {};
|
||||
|
||||
modal_body = $('<div>');
|
||||
|
||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||
|
||||
var currentDomainIDType = getCurrentDomainIDType();
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||
warning += "</div>";
|
||||
modal_body.append(warning);
|
||||
}
|
||||
|
||||
// setup a select box for the returned places
|
||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||
_.each(data.data.places, function(place) {
|
||||
places_by_id[place.id] = place;
|
||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||
})
|
||||
modal_body.append(place_select);
|
||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||
|
||||
if (forcePathTo === undefined || forcePathTo === null) {
|
||||
var path = "<div class='form-group'>";
|
||||
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||
path += "</div>";
|
||||
modal_body.append($(path));
|
||||
}
|
||||
|
||||
var place_select = modal_body.find("#place-name-select")
|
||||
place_select.change(function(ev) {
|
||||
var warning = modal_body.find("#place-name-warning");
|
||||
var place = places_by_id[$(this).val()];
|
||||
if (place === undefined || place.pointee === null) {
|
||||
warning.hide();
|
||||
} else {
|
||||
warning.show();
|
||||
}
|
||||
});
|
||||
place_select.trigger('change');
|
||||
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
|
||||
className: 'add-place-confirm-button btn btn-primary',
|
||||
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 || forcePathTo === null) {
|
||||
var placePath = $('#place-path-input').val();
|
||||
} else {
|
||||
var placePath = forcePathTo;
|
||||
}
|
||||
|
||||
$('.add-place-confirm-button').attr('disabled', 'disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
|
||||
$('.add-place-cancel-button').attr('disabled', 'disabled');
|
||||
|
||||
function finalizeSaveDomainID(domainID) {
|
||||
var jsonSettings = {
|
||||
metaverse: {
|
||||
id: domainID
|
||||
}
|
||||
}
|
||||
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(jsonSettings),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done(function(data) {
|
||||
if (data.status == "success") {
|
||||
waitForDomainServerRestart(function() {
|
||||
dialog.modal('hide');
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bootbox.alert("Failed to add place");
|
||||
}
|
||||
}).fail(function() {
|
||||
bootbox.alert("Failed to add place");
|
||||
});
|
||||
}
|
||||
|
||||
// If domainID is not specified, the current domain id will be used.
|
||||
function finishSettingUpPlace(domainID) {
|
||||
sendUpdatePlaceRequest(
|
||||
placeID,
|
||||
placePath,
|
||||
domainID,
|
||||
false,
|
||||
function(data) {
|
||||
dialog.modal('hide')
|
||||
if (domainID) {
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||
finalizeSaveDomainID(domainID);
|
||||
} else {
|
||||
if (onSuccessfullyAdded) {
|
||||
onSuccessfullyAdded(places_by_id[placeID].name);
|
||||
}
|
||||
}
|
||||
},
|
||||
function(data) {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function maybeCreateNewDomainID() {
|
||||
console.log("Maybe creating domain id", currentDomainIDType)
|
||||
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
|
||||
finishSettingUpPlace();
|
||||
} else {
|
||||
sendCreateDomainRequest(function(domainID) {
|
||||
console.log("Created domain", domainID);
|
||||
finishSettingUpPlace(domainID);
|
||||
}, function() {
|
||||
$('.add-place-confirm-button').removeAttr('disabled');
|
||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||
$('.add-place-cancel-button').removeAttr('disabled');
|
||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||
bootbox.alert("FAIL");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
maybeCreateNewDomainID();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modal_buttons["success"] = {
|
||||
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
|
||||
callback: function() {
|
||||
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
|
||||
}
|
||||
|
||||
dialog = bootbox.dialog({
|
||||
title: Strings.ADD_PLACE_TITLE,
|
||||
message: modal_body,
|
||||
closeButton: false,
|
||||
buttons: modal_buttons
|
||||
});
|
||||
} else {
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||
},
|
||||
complete: function() {
|
||||
loadingDialog.modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var domainType = getCurrentDomainIDType();
|
||||
if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
|
||||
loadPlaces();
|
||||
} else {
|
||||
getDomainFromAPI(function(data) {
|
||||
if (data.status === 'success') {
|
||||
var domainType = getCurrentDomainIDType();
|
||||
loadPlaces();
|
||||
} else {
|
||||
loadingDialog.modal('hide');
|
||||
bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
|
||||
if (response) {
|
||||
chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
bootbox.alert({
|
||||
title: Strings.ADD_PLACE_NOT_CONNECTED_TITLE,
|
||||
message: Strings.ADD_PLACE_NOT_CONNECTED_MESSAGE
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function sendCreateDomainRequest(onSuccess, onError) {
|
||||
$.ajax({
|
||||
url: '/api/domains',
|
||||
dataType: 'json',
|
||||
type: 'POST',
|
||||
data: { label: "" },
|
||||
success: function(data) {
|
||||
onSuccess(data.domain.id);
|
||||
},
|
||||
error: onError
|
||||
});
|
||||
}
|
||||
|
||||
function waitForDomainServerRestart(callback) {
|
||||
function checkForDomainUp() {
|
||||
$.ajax('', {
|
||||
success: function() {
|
||||
callback();
|
||||
},
|
||||
error: function() {
|
||||
setTimeout(checkForDomainUp, 50);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(checkForDomainUp, 10);
|
||||
|
||||
}
|
||||
|
||||
function prepareAccessTokenPrompt(callback) {
|
||||
swal({
|
||||
title: "Connect Account",
|
||||
type: "input",
|
||||
text: "Paste your created access token here." +
|
||||
"</br></br>If you did not successfully create an access token click cancel below and attempt to connect your account again.</br></br>",
|
||||
showCancelButton: true,
|
||||
closeOnConfirm: false,
|
||||
html: true
|
||||
}, function(inputValue){
|
||||
if (inputValue === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputValue === "") {
|
||||
swal.showInputError("Please paste your access token in the input field.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(inputValue);
|
||||
}
|
||||
|
||||
swal.close();
|
||||
});
|
||||
}
|
|
@ -9,16 +9,18 @@
|
|||
|
||||
<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" 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 %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% if (!group.hidden) { %>
|
||||
<% panelID = group.name ? group.name : group.html_id %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
|
@ -26,49 +28,63 @@
|
|||
</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 id="manage-cloud-domains-link" style="display: none;">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
|
||||
<div id="xs-advanced-container" class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button id="advanced-toggle-button-xs" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span class="alert-link">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
<% if (!group.hidden) { %>
|
||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
|
@ -85,8 +101,9 @@
|
|||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='js/bootstrap-switch.min.js'></script>
|
||||
<script src='js/settings.js'></script>
|
||||
<script src='/js/sha256.js'></script>
|
||||
<script src='js/form2js.min.js'></script>
|
||||
<script src='js/sha256.js'></script>
|
||||
<script src='js/bootstrap-switch.min.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='js/settings.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
File diff suppressed because it is too large
Load diff
89
domain-server/resources/web/wizard/css/style.css
Normal file
89
domain-server/resources/web/wizard/css/style.css
Normal file
|
@ -0,0 +1,89 @@
|
|||
label {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
|
||||
color: #373A3C;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
margin-bottom: 20px;
|
||||
line-height: 26px;
|
||||
font-size: 24px;
|
||||
|
||||
color: #373A3C;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
|
||||
color: #818A91;
|
||||
}
|
||||
|
||||
.step-info {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
|
||||
color: #373A3C;
|
||||
}
|
||||
|
||||
#admin-row {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#connect-question {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#connect-account-btn {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 205px;
|
||||
}
|
||||
|
||||
#place-name-group {
|
||||
margin-top: 42px;
|
||||
margin-bottom: 140px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#place-name-link {
|
||||
line-height: 38px;
|
||||
font-size: 32px;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
color: #373A3C;
|
||||
}
|
||||
|
||||
#place-name-edit {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#change-place-name {
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#rez-options-row {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
#verify-password-row {
|
||||
margin-bottom: 33px;
|
||||
}
|
||||
|
||||
#checkmark-image {
|
||||
margin-top: 66px;
|
||||
margin-bottom: 59px;
|
||||
width: 169px;
|
||||
height: 169px;
|
||||
}
|
||||
|
||||
#visit-domain-row {
|
||||
margin-bottom: 68px;
|
||||
}
|
29
domain-server/resources/web/wizard/header.html
Normal file
29
domain-server/resources/web/wizard/header.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>domain-server</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/style.css" rel="stylesheet" media="screen">
|
||||
<link href="/wizard/css/style.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
|
||||
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
|
||||
|
||||
<script src='/js/sweetalert.min.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||||
<div class="container-fluid">
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<a class="navbar-brand step-info" href="#">Setup Wizard (Domain Server Settings)</a>
|
||||
|
||||
<div class="navbar-form navbar-right">
|
||||
<button id="skip-wizard-button" type="button" class="btn btn-default" style="display:none;">Skip Wizard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
229
domain-server/resources/web/wizard/index.shtml
Normal file
229
domain-server/resources/web/wizard/index.shtml
Normal file
|
@ -0,0 +1,229 @@
|
|||
<!--#include virtual="wizard/header.html"-->
|
||||
<div class="wizard-step desktop-only col-md-6 col-centered" style="display: none;">
|
||||
<h4 class="step-title"></h4>
|
||||
<dl class="row">
|
||||
<dd class="col-md-12">
|
||||
<span class='step-description'>By connecting your High Fidelity Account you will be granting access to your account information.</span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dd class="col-md-12">
|
||||
<a id="connect-account-btn" role="button" class="btn btn-primary btn-md btn-block" target="_blank">Connect your High Fidelity account</a>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-3">
|
||||
<button type="button" class="btn btn-md btn-block btn-default next-button">Skip</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step col-md-8 col-centered" style="display: none;">
|
||||
<h4 class="step-title"></h4>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span class='step-description'>
|
||||
<a target='_blank' href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/place-names'>Place names</a> are similar to web addresses. Users who want to visit your domain can
|
||||
enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.</br>
|
||||
People can also use your <b>domain's IP address (shown below)</b> to visit your High Fidelity domain.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="centered-hack-parent">
|
||||
<div id="place-name-group" class="centered-hack">
|
||||
<p id="place-name-link"></p>
|
||||
<div id="place-name-edit">
|
||||
<span class='glyphicon glyphicon-pencil'></span>
|
||||
<a href="#" id="change-place-name">Choose a custom Place Name instead</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-3">
|
||||
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
|
||||
</dd>
|
||||
<dd class="col-md-3 col-md-offset-6">
|
||||
<button type="button" class="btn btn-md btn-block btn-primary next-button">Next</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step col-md-9 col-centered" style="display: none;">
|
||||
<h4 class="step-title"></h4>
|
||||
<div class="row">
|
||||
<p id="permissions-description" class="col-md-12 step-info"><b>Localhost</b> has been granted administrator privileges to this domain. (Localhost is any</br>user on the same machine as the High Fidelity Server)</p>
|
||||
</div>
|
||||
<div id="admin-row" class="row">
|
||||
<p class="col-md-6">
|
||||
<span id="admin-description" class="step-info"><b>Add your High Fidelity username</b> and any other usernames to grant administrator privileges.</span>
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle="tooltip" title="Users who will have all the permissions for this domain."></span>
|
||||
</p>
|
||||
<div class="col-md-6">
|
||||
<input id="admin-usernames" type="text" class="form-control">
|
||||
<span class="help-block text-right">separate by commas (user1, user2,..)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span class="step-description">Grant basic permissions to other users. You can change these later.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p id="connect-question" class="step-info">
|
||||
Who can connect to your domain?
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to connect to this domain.'></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="connect-none" name="connect-radio" type="radio" value="none" checked> None
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Only the admins of this domain'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-3">
|
||||
<label>
|
||||
<input id="connect-friends" name="connect-radio" type="radio" value="friends"> Friends
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are your Friends in High Fidelity'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-5">
|
||||
<label>
|
||||
<input id="connect-logged-in" name="connect-radio" type="radio" value="logged-in"> Users logged in to High Fidelity
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are currently logged into High Fidelity'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="connect-everyone" name="connect-radio" type="radio" value="everyone"> Everyone
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title="Users who aren't logged into High Fidelity"></span>
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="step-info">
|
||||
Who can rez items in your domain?
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to create entities in this domain.'></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="rez-options-row" class="row">
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="rez-none" name="rez-radio" type="radio" value="none" checked> None
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Only the admins of this domain'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-3">
|
||||
<label>
|
||||
<input id="rez-friends" name="rez-radio" type="radio" value="friends"> Friends
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are your Friends in High Fidelity'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-5">
|
||||
<label>
|
||||
<input id="rez-logged-in" name="rez-radio" type="radio" value="logged-in"> Users logged in to High Fidelity
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='Users who are currently logged into High Fidelity'></span>
|
||||
</label>
|
||||
</p>
|
||||
<p class="col-md-2">
|
||||
<label>
|
||||
<input id="rez-everyone" name="rez-radio" type="radio" value="everyone"> Everyone
|
||||
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title="Users who aren't logged into High Fidelity"></span>
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-md-offset-9">
|
||||
<button id="save-permissions" type="button" class="btn btn-md btn-block btn-primary">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step cloud-only col-md-7 col-centered" style="display: none;">
|
||||
<h4 class="step-title"></h4>
|
||||
<dl class="row">
|
||||
<dd class="col-md-12">
|
||||
<span class='step-description'>
|
||||
Your server settings are currently accessible without a username and password.
|
||||
Adding credentials will ensure that only authorized users have access to modify the settings.
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="row">
|
||||
<p class="col-md-12 step-info">Create a username and password to secure the access to your domain server settings.</p>
|
||||
</div>
|
||||
<dl class="row">
|
||||
<dt class="col-md-4 step-info">Username</dt>
|
||||
<dd class="col-md-8">
|
||||
<input id="http_username" type="text" class="form-control">
|
||||
<span class='help-block'>This does not have to be your High Fidelity username</span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-md-4 step-info">Enter password</dt>
|
||||
<dd class="col-md-8">
|
||||
<input id="http_password" type="password" class="form-control">
|
||||
<span class='help-block'>This should not be the same as your High Fidelity password</span>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl id="verify-password-row" class="row">
|
||||
<dt class="col-md-4 step-info">Re-enter password</dt>
|
||||
<dd class="col-md-8">
|
||||
<input id="verify_http_password" type="password" class="form-control">
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-3 col-md-offset-9">
|
||||
<button id="save-username-password" type="button" class="btn btn-md btn-block btn-primary">Finish</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step cloud-only col-md-7 col-centered" style="display: none;">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 col-centered">
|
||||
<img id="checkmark-image" src="../images/checkmark.svg">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="step-info">Congratulations! You have successfully setup and configured your cloud hosted domain.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="visit-domain-row" class="row">
|
||||
<div class="col-md-12">
|
||||
<label><input id="go-to-domain" class="form-check-input" type="checkbox"> Visit domain in VR now</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="row">
|
||||
<dd class="col-md-5 col-md-offset-7">
|
||||
<button id="explore-settings" type="button" class="btn btn-md btn-block btn-primary">Explore all domain server settings</button>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/sha256.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='js/wizard.js'></script>
|
||||
<!--#include virtual="page-end.html"-->
|
472
domain-server/resources/web/wizard/js/wizard.js
Normal file
472
domain-server/resources/web/wizard/js/wizard.js
Normal file
|
@ -0,0 +1,472 @@
|
|||
var Metaverse = {
|
||||
accessToken: null
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
Strings.ADD_PLACE_NOT_CONNECTED_MESSAGE = "You must have an access token to query your High Fidelity places.<br><br>" +
|
||||
"Please go back and connect your account.";
|
||||
|
||||
$('#connect-account-btn').attr('href', URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true");
|
||||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
$('body').on('click', '.next-button', function() {
|
||||
goToNextStep();
|
||||
});
|
||||
|
||||
$('body').on('click', '.back-button', function() {
|
||||
goToPreviousStep();
|
||||
});
|
||||
|
||||
$('body').on('click', '#skip-wizard-button', function() {
|
||||
skipWizard();
|
||||
})
|
||||
|
||||
$('body').on('click', '#connect-account-btn', function() {
|
||||
$(this).blur();
|
||||
prepareAccessTokenPrompt(function(accessToken) {
|
||||
Metaverse.accessToken = accessToken;
|
||||
saveAccessToken();
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('click', '#save-permissions', function() {
|
||||
savePermissions();
|
||||
});
|
||||
|
||||
function triggerSaveUsernamePassword(event) {
|
||||
if (event.keyCode === 13) {
|
||||
$("#save-username-password").click();
|
||||
}
|
||||
}
|
||||
$("#http_username").keyup(triggerSaveUsernamePassword);
|
||||
$("#http_password").keyup(triggerSaveUsernamePassword);
|
||||
$("#verify_http_password").keyup(triggerSaveUsernamePassword);
|
||||
$('body').on('click', '#save-username-password', function() {
|
||||
saveUsernamePassword();
|
||||
});
|
||||
|
||||
$('body').on('click', '#change-place-name', function() {
|
||||
chooseFromHighFidelityPlaces(Settings.data.values.metaverse.access_token, "/0,-10,0", function(placeName) {
|
||||
updatePlaceNameLink(placeName);
|
||||
});
|
||||
});
|
||||
|
||||
$('body').on('click', '#explore-settings', function() {
|
||||
exploreSettings();
|
||||
});
|
||||
|
||||
reloadSettings(function(success) {
|
||||
if (success) {
|
||||
getDomainFromAPI();
|
||||
setupWizardSteps();
|
||||
updatePlaceNameDisplay();
|
||||
updateUsernameDisplay();
|
||||
} else {
|
||||
swal({
|
||||
title: '',
|
||||
type: 'error',
|
||||
text: "There was a problem loading the domain settings.\nPlease refresh the page to try again.",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function setupWizardSteps() {
|
||||
var stepsCompleted = Settings.data.values.wizard.steps_completed;
|
||||
var steps = null;
|
||||
|
||||
if (Settings.data.values.wizard.cloud_domain) {
|
||||
$('.desktop-only').remove();
|
||||
$('.wizard-step').find('.back-button').hide();
|
||||
|
||||
steps = $('.wizard-step');
|
||||
$(steps).each(function(i) {
|
||||
$(this).children(".step-title").text("Step " + (i + 1) + " of " + (steps.length - 1));
|
||||
});
|
||||
|
||||
$('#permissions-description').html('You <span id="username-display"></span>have been assigned administrator privileges to this domain.');
|
||||
$('#admin-description').html('Add more High Fidelity usernames to grant administrator privileges.');
|
||||
} else {
|
||||
$('.cloud-only').remove();
|
||||
$('#save-permissions').text("Finish");
|
||||
|
||||
steps = $('.wizard-step');
|
||||
$(steps).each(function(i) {
|
||||
$(this).children(".step-title").text("Step " + (i + 1) + " of " + steps.length);
|
||||
});
|
||||
|
||||
if (stepsCompleted == 0) {
|
||||
$('#skip-wizard-button').show();
|
||||
}
|
||||
}
|
||||
|
||||
var currentStep = steps[stepsCompleted];
|
||||
$(currentStep).show();
|
||||
}
|
||||
|
||||
function updatePlaceNameLink(address) {
|
||||
if (address) {
|
||||
$('#place-name-link').html('Your domain is reachable at: <a target="_blank" href="' + URLs.PLACE_URL + '/' + address + '">' + address + '</a>');
|
||||
}
|
||||
}
|
||||
|
||||
function updatePlaceNameDisplay() {
|
||||
if (Settings.data.values.metaverse.id) {
|
||||
$.getJSON(URLs.METAVERSE_URL + '/api/v1/domains/' + Settings.data.values.metaverse.id, function(data) {
|
||||
|
||||
if (data.status === 'success') {
|
||||
if (data.domain.default_place_name) {
|
||||
// Place name
|
||||
updatePlaceNameLink(data.domain.default_place_name);
|
||||
} else if (data.domain.name) {
|
||||
// Temporary name
|
||||
updatePlaceNameLink(data.domain.name);
|
||||
} else if (data.domain.network_address) {
|
||||
if (data.domain.network_port !== 40102) {
|
||||
// IP:PORT
|
||||
updatePlaceNameLink(data.domain.network_address + ':' + data.domain.network_port);
|
||||
} else {
|
||||
// IP
|
||||
updatePlaceNameLink(data.domain.network_address);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('Request Failed');
|
||||
}
|
||||
}).fail(function() {
|
||||
console.warn('Request Failed');
|
||||
});
|
||||
} else {
|
||||
console.warn('No metaverse domain ID!');
|
||||
}
|
||||
}
|
||||
|
||||
function updateUsernameDisplay() {
|
||||
var permissions = Settings.data.values.security.permissions;
|
||||
if (permissions.length > 0) {
|
||||
$('#username-display').html('<b>(' + permissions[0].permissions_id + ')</b> ');
|
||||
}
|
||||
}
|
||||
|
||||
function reloadSettings(callback) {
|
||||
$.getJSON('/settings.json', function(data){
|
||||
Settings.data = data;
|
||||
|
||||
if (callback) {
|
||||
// call the callback now that settings are loaded
|
||||
callback(true);
|
||||
}
|
||||
}).fail(function() {
|
||||
if (callback) {
|
||||
// call the failure object since settings load failed
|
||||
callback(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function postSettings(jsonSettings, callback) {
|
||||
console.log("----- SAVING ------");
|
||||
console.log(JSON.stringify(jsonSettings));
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
$.ajax('/settings.json', {
|
||||
data: JSON.stringify(jsonSettings),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done(function(data){
|
||||
if (data.status == "success") {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
reloadSettings();
|
||||
} else {
|
||||
swal("Error", Strings.LOADING_SETTINGS_ERROR)
|
||||
reloadSettings();
|
||||
}
|
||||
}).fail(function(){
|
||||
swal("Error", Strings.LOADING_SETTINGS_ERROR)
|
||||
reloadSettings();
|
||||
});
|
||||
}
|
||||
|
||||
function goToNextStep() {
|
||||
$('#skip-wizard-button').hide();
|
||||
|
||||
var currentStep = $('body').find('.wizard-step:visible');
|
||||
var nextStep = currentStep.next('.wizard-step');
|
||||
|
||||
var formJSON = {
|
||||
"wizard": {}
|
||||
}
|
||||
|
||||
if (nextStep.length > 0) {
|
||||
currentStep.hide();
|
||||
nextStep.show();
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) + 1;
|
||||
|
||||
postSettings({
|
||||
"wizard": {
|
||||
"steps_completed": currentStepNumber.toString()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
postSettings({
|
||||
"wizard": {
|
||||
"steps_completed": "0",
|
||||
"completed_once": true
|
||||
}
|
||||
}, redirectToSettings);
|
||||
}
|
||||
}
|
||||
|
||||
function goToPreviousStep() {
|
||||
var currentStep = $('body').find('.wizard-step:visible');
|
||||
var previousStep = currentStep.prev('.wizard-step');
|
||||
|
||||
var formJSON = {
|
||||
"wizard": {}
|
||||
}
|
||||
|
||||
if (previousStep.length > 0) {
|
||||
currentStep.hide();
|
||||
previousStep.show();
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) - 1;
|
||||
|
||||
postSettings({
|
||||
"wizard": {
|
||||
"steps_completed": currentStepNumber.toString()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function skipWizard() {
|
||||
postSettings({
|
||||
"wizard": {
|
||||
"steps_completed": "0",
|
||||
"completed_once": true
|
||||
}
|
||||
}, redirectToSettings);
|
||||
}
|
||||
|
||||
function redirectToSettings() {
|
||||
var redirectURL = "/settings" + location.search;
|
||||
if (Settings.data.values.wizard.cloud_domain) {
|
||||
if (location.search.length > 0) {
|
||||
redirectURL += "&";
|
||||
} else {
|
||||
redirectURL += "?";
|
||||
}
|
||||
redirectURL += "cloud-wizard-exit";
|
||||
}
|
||||
location.href = redirectURL;
|
||||
}
|
||||
|
||||
function saveAccessToken() {
|
||||
var formJSON = {
|
||||
"metaverse": {}
|
||||
}
|
||||
if (Metaverse.accessToken) {
|
||||
formJSON.metaverse.access_token = Metaverse.accessToken;
|
||||
}
|
||||
|
||||
// remove focus from the button
|
||||
$(this).blur();
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
postSettings(formJSON, goToNextStep);
|
||||
}
|
||||
|
||||
function getSettingDescriptionForKey(groupKey, settingKey) {
|
||||
for (var i in Settings.data.descriptions) {
|
||||
var group = Settings.data.descriptions[i];
|
||||
if (group.name === groupKey) {
|
||||
for (var j in group.settings) {
|
||||
var setting = group.settings[j];
|
||||
if (setting.name === settingKey) {
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function savePermissions() {
|
||||
var localhostPermissions = (Settings.data.values.wizard.cloud_domain !== true)
|
||||
var anonymousCanConnect = false;
|
||||
var friendsCanConnect = false;
|
||||
var loggedInCanConnect = false;
|
||||
var anonymousCanRez = false;
|
||||
var friendsCanRez = false;
|
||||
var loggedInCanRez = false;
|
||||
|
||||
var connectValue = $('input[name=connect-radio]:checked').val();
|
||||
var rezValue = $('input[name=rez-radio]:checked').val();
|
||||
|
||||
switch (connectValue) {
|
||||
case "friends":
|
||||
friendsCanConnect = true;
|
||||
break;
|
||||
case "logged-in":
|
||||
friendsCanConnect = true;
|
||||
loggedInCanConnect = true;
|
||||
break;
|
||||
case "everyone":
|
||||
anonymousCanConnect = true;
|
||||
friendsCanConnect = true;
|
||||
loggedInCanConnect = true;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rezValue) {
|
||||
case "friends":
|
||||
friendsCanRez = true;
|
||||
break;
|
||||
case "logged-in":
|
||||
friendsCanRez = true;
|
||||
loggedInCanRez = true;
|
||||
break;
|
||||
case "everyone":
|
||||
anonymousCanRez = true;
|
||||
friendsCanRez = true;
|
||||
loggedInCanRez = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var admins = $('#admin-usernames').val().split(',');
|
||||
|
||||
var existingAdmins = Settings.data.values.security.permissions.map(function(value) {
|
||||
return value.permissions_id;
|
||||
});
|
||||
admins = admins.concat(existingAdmins);
|
||||
|
||||
// Filter out unique values
|
||||
admins = _.uniq(admins.map(function(username) {
|
||||
return username.trim();
|
||||
})).filter(function(username) {
|
||||
return username !== "";
|
||||
});
|
||||
|
||||
var formJSON = {
|
||||
"security": {
|
||||
"standard_permissions": [
|
||||
{
|
||||
"id_can_connect": anonymousCanConnect,
|
||||
"id_can_rez": anonymousCanRez,
|
||||
"id_can_rez_certified": anonymousCanRez,
|
||||
"id_can_rez_tmp": anonymousCanRez,
|
||||
"id_can_rez_tmp_certified": anonymousCanRez,
|
||||
"permissions_id": "anonymous"
|
||||
},
|
||||
{
|
||||
"id_can_connect": friendsCanConnect,
|
||||
"id_can_rez": friendsCanRez,
|
||||
"id_can_rez_certified": friendsCanRez,
|
||||
"id_can_rez_tmp": friendsCanRez,
|
||||
"id_can_rez_tmp_certified": friendsCanRez,
|
||||
"permissions_id": "friends"
|
||||
},
|
||||
{
|
||||
"id_can_connect": loggedInCanConnect,
|
||||
"id_can_rez": loggedInCanRez,
|
||||
"id_can_rez_certified": loggedInCanRez,
|
||||
"id_can_rez_tmp": loggedInCanRez,
|
||||
"id_can_rez_tmp_certified": loggedInCanRez,
|
||||
"permissions_id": "logged-in"
|
||||
},
|
||||
{
|
||||
"id_can_adjust_locks": localhostPermissions,
|
||||
"id_can_connect": localhostPermissions,
|
||||
"id_can_connect_past_max_capacity": localhostPermissions,
|
||||
"id_can_kick": localhostPermissions,
|
||||
"id_can_replace_content": localhostPermissions,
|
||||
"id_can_rez": localhostPermissions,
|
||||
"id_can_rez_certified": localhostPermissions,
|
||||
"id_can_rez_tmp": localhostPermissions,
|
||||
"id_can_rez_tmp_certified": localhostPermissions,
|
||||
"id_can_write_to_asset_server": localhostPermissions,
|
||||
"permissions_id": "localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
if (admins.length > 0) {
|
||||
formJSON.security.permissions = [];
|
||||
|
||||
var permissionsDesc = getSettingDescriptionForKey("security", "permissions");
|
||||
for (var i in admins) {
|
||||
var admin = admins[i];
|
||||
var perms = {};
|
||||
for (var i in permissionsDesc.columns) {
|
||||
var name = permissionsDesc.columns[i].name;
|
||||
if (name === "permissions_id") {
|
||||
perms[name] = admin;
|
||||
} else {
|
||||
perms[name] = true;
|
||||
}
|
||||
}
|
||||
formJSON.security.permissions.push(perms);
|
||||
}
|
||||
}
|
||||
|
||||
// remove focus from the button
|
||||
$(this).blur();
|
||||
|
||||
postSettings(formJSON, goToNextStep);
|
||||
}
|
||||
|
||||
function saveUsernamePassword() {
|
||||
var username = $("#http_username").val();
|
||||
var password = $("#http_password").val();
|
||||
var verify_password = $("#verify_http_password").val();
|
||||
|
||||
if (username.length == 0) {
|
||||
bootbox.alert({ "message": "You must set a username!", "title": "Username Error" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length == 0) {
|
||||
bootbox.alert({ "message": "You must set a password!", "title": "Password Error" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password != verify_password) {
|
||||
bootbox.alert({ "message": "Passwords must match!", "title": "Password Error" });
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStepNumber = parseInt(Settings.data.values.wizard.steps_completed) + 1;
|
||||
|
||||
var formJSON = {
|
||||
"security": {
|
||||
"http_username": username,
|
||||
"http_password": sha256_digest(password)
|
||||
},
|
||||
"wizard": {
|
||||
"steps_completed": currentStepNumber.toString()
|
||||
}
|
||||
}
|
||||
|
||||
// remove focus from the button
|
||||
$(this).blur();
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
postSettings(formJSON, function() {
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function exploreSettings() {
|
||||
if ($('#go-to-domain').is(":checked")) {
|
||||
var link = $('#place-name-link a:first');
|
||||
if (link.length > 0) {
|
||||
window.open(link.attr("href"));
|
||||
}
|
||||
}
|
||||
|
||||
goToNextStep();
|
||||
}
|
|
@ -814,9 +814,15 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
|||
|
||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
||||
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
||||
// before we respond to this ICE ping packet, make sure we have a peer in the list that matches
|
||||
QUuid icePeerID = QUuid::fromRfc4122({ message->getRawMessage(), NUM_BYTES_RFC4122_UUID });
|
||||
|
||||
if (_icePeers.contains(icePeerID)) {
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
||||
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
#include "DomainServerNodeData.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
|
||||
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||
|
||||
#if USE_STABLE_GLOBAL_SERVICES
|
||||
|
@ -54,6 +56,82 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
|
|||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
|
||||
#endif
|
||||
|
||||
bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
|
||||
const QString& metaversePath,
|
||||
const QString& requestSubobjectKey,
|
||||
std::initializer_list<QString> requiredData,
|
||||
std::initializer_list<QString> optionalData,
|
||||
bool requireAccessToken) {
|
||||
|
||||
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
|
||||
if (accessTokenVariant == nullptr && requireAccessToken) {
|
||||
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject subobject;
|
||||
|
||||
auto params = connection->parseUrlEncodedForm();
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
root.insert(requestSubobjectKey, subobject);
|
||||
QJsonDocument doc { root };
|
||||
|
||||
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + metaversePath };
|
||||
|
||||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
if (accessTokenVariant != nullptr) {
|
||||
auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString();
|
||||
req.setRawHeader("Authorization", accessTokenHeader.toLatin1());
|
||||
}
|
||||
|
||||
QNetworkReply* reply;
|
||||
auto method = connection->requestOperation();
|
||||
if (method == QNetworkAccessManager::GetOperation) {
|
||||
reply = NetworkAccessManager::getInstance().get(req);
|
||||
} else if (method == QNetworkAccessManager::PostOperation) {
|
||||
reply = NetworkAccessManager::getInstance().post(req, doc.toJson());
|
||||
} else if (method == QNetworkAccessManager::PutOperation) {
|
||||
reply = NetworkAccessManager::getInstance().put(req, doc.toJson());
|
||||
} else {
|
||||
connection->respond(HTTPConnection::StatusCode400, "Error forwarding request, unsupported method");
|
||||
return true;
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
auto data = reply->readAll();
|
||||
qDebug() << "Got error response from metaverse server (" << reply->url() << "): " << data << reply->errorString();
|
||||
connection->respond(HTTPConnection::StatusCode400, data);
|
||||
return;
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_gatekeeper(this),
|
||||
|
@ -614,8 +692,6 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
|
||||
}
|
||||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
|
||||
bool DomainServer::resetAccountManagerAccessToken() {
|
||||
if (!_oauthProviderURL.isEmpty()) {
|
||||
// check for an access-token in our settings, can optionally be overidden by env value
|
||||
|
@ -754,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
void DomainServer::updateICEServerAddresses() {
|
||||
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
|
||||
// there seems to be a 5.9 bug where lookupHost never calls our slot
|
||||
// so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet
|
||||
static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000;
|
||||
QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::timeoutICEAddressLookup() {
|
||||
if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) {
|
||||
// we waited 5s and didn't hear back for our ICE DNS lookup
|
||||
// so time that one out and kick off another
|
||||
|
||||
qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying";
|
||||
|
||||
QHostInfo::abortHostLookup(_iceAddressLookupID);
|
||||
|
||||
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
||||
|
||||
updateICEServerAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1731,6 +1787,8 @@ 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) {
|
||||
const QString JSON_MIME_TYPE = "application/json";
|
||||
|
@ -1740,15 +1798,23 @@ 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_DOMAINS = "/api/domains";
|
||||
const QString URI_API_DOMAINS_ID = "/api/domains/";
|
||||
|
||||
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}";
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// allow sub-handlers to handle requests that do not require authentication
|
||||
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
|
||||
auto getSetting = [this](QString keyPath, QVariant& value) -> bool {
|
||||
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||
QVariant* var = valueForKeyPath(settingsMap, keyPath);
|
||||
if (var == nullptr) {
|
||||
return false;
|
||||
}
|
||||
value = *var;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// check if this is a request for a scripted assignment (with a temp unique UUID)
|
||||
const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING);
|
||||
|
@ -1810,6 +1876,31 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
return true;
|
||||
}
|
||||
|
||||
// Check if we should redirect/prevent access to the wizard
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||
const QString URI_WIZARD = "/wizard/";
|
||||
const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once";
|
||||
const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH);
|
||||
const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool();
|
||||
|
||||
if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) {
|
||||
// First visit, redirect to the wizard
|
||||
QUrl redirectedURL = url;
|
||||
redirectedURL.setPath(URI_WIZARD);
|
||||
|
||||
Headers redirectHeaders;
|
||||
redirectHeaders.insert("Location", redirectedURL.toEncoded());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode302,
|
||||
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
|
||||
return true;
|
||||
} else if (url.path() == URI_WIZARD && completedOnce) {
|
||||
// Wizard already completed, return 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||
if (url.path() == "/assignments.json") {
|
||||
// user is asking for json list of assignments
|
||||
|
@ -1899,6 +1990,13 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
connection->respond(HTTPConnection::StatusCode200);
|
||||
restart();
|
||||
return true;
|
||||
} else if (url.path() == URI_API_DOMAINS) {
|
||||
return forwardMetaverseAPIRequest(connection, "/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);
|
||||
} else if (url.path() == URI_API_PLACES) {
|
||||
return forwardMetaverseAPIRequest(connection, "/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);
|
||||
|
@ -1978,8 +2076,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
// this is an entity file upload, ask the HTTPConnection to parse the data
|
||||
QList<FormData> formData = connection->parseFormData();
|
||||
|
||||
Headers redirectHeaders;
|
||||
|
||||
if (formData.size() > 0 && formData[0].second.size() > 0) {
|
||||
// invoke our method to hand the new octree file off to the octree server
|
||||
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||
|
@ -1993,7 +2089,98 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
}
|
||||
|
||||
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_DOMAINS) {
|
||||
|
||||
return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "domain", { "label" });
|
||||
}
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) {
|
||||
if (url.path() == URI_API_DOMAINS) {
|
||||
QVariant domainSetting;
|
||||
if (!getSetting(METAVERSE_DOMAIN_ID_KEY_PATH, domainSetting)) {
|
||||
connection->respond(HTTPConnection::StatusCode400, "Domain id has not been set");
|
||||
return true;
|
||||
}
|
||||
auto domainID = domainSetting.toString();
|
||||
return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain",
|
||||
{ }, { "network_address", "network_port", "label" });
|
||||
} else if (url.path() == URI_API_PLACES) {
|
||||
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
|
||||
if (!accessTokenVariant->isValid()) {
|
||||
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
|
||||
return true;
|
||||
}
|
||||
|
||||
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 domainID;
|
||||
if (it == params.end()) {
|
||||
QVariant domainSetting;
|
||||
if (!getSetting(METAVERSE_DOMAIN_ID_KEY_PATH, domainSetting)) {
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
return true;
|
||||
}
|
||||
domainID = domainSetting.toString();
|
||||
} else {
|
||||
domainID = it.value();
|
||||
}
|
||||
|
||||
QJsonObject root {
|
||||
{
|
||||
"place",
|
||||
QJsonObject({
|
||||
{ "pointee_query", domainID },
|
||||
{ "path", path }
|
||||
})
|
||||
}
|
||||
};
|
||||
QJsonDocument doc(root);
|
||||
|
||||
|
||||
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/v1/places/" + place_id };
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -2037,7 +2224,6 @@ static const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
|
|||
static const QString STATE_QUERY_KEY = "state";
|
||||
|
||||
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url, bool skipSubHandler) {
|
||||
qDebug() << "HTTPS request received at" << url.toString();
|
||||
if (url.path() == URI_OAUTH) {
|
||||
|
||||
QUrlQuery codeURLQuery(url);
|
||||
|
@ -2298,6 +2484,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);
|
||||
|
@ -2799,9 +2987,20 @@ void DomainServer::handleKeypairChange() {
|
|||
|
||||
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
||||
// clear the ICE address lookup ID so that it can fire again
|
||||
_iceAddressLookupID = -1;
|
||||
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError) {
|
||||
// enumerate the returned addresses and collect only valid IPv4 addresses
|
||||
QList<QHostAddress> sanitizedAddresses = hostInfo.addresses();
|
||||
auto it = sanitizedAddresses.begin();
|
||||
while (it != sanitizedAddresses.end()) {
|
||||
if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
++it;
|
||||
} else {
|
||||
it = sanitizedAddresses.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
|
||||
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||
|
||||
// if we don't have an ICE server to use yet, trigger a retry
|
||||
|
@ -2814,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
} else {
|
||||
int countBefore = _iceServerAddresses.count();
|
||||
|
||||
_iceServerAddresses = hostInfo.addresses();
|
||||
_iceServerAddresses = sanitizedAddresses;
|
||||
|
||||
if (countBefore == 0) {
|
||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||
|
|
|
@ -116,8 +116,6 @@ private slots:
|
|||
void tokenGrantFinished();
|
||||
void profileRequestFinished();
|
||||
|
||||
void timeoutICEAddressLookup();
|
||||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
|
@ -187,6 +185,13 @@ private:
|
|||
|
||||
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
|
||||
|
||||
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
|
||||
const QString& metaversePath,
|
||||
const QString& requestSubobject,
|
||||
std::initializer_list<QString> requiredData = { },
|
||||
std::initializer_list<QString> optionalData = { },
|
||||
bool requireAccessToken = true);
|
||||
|
||||
SubnetList _acSubnetWhitelist;
|
||||
|
||||
std::vector<QString> _replicatedUsernames;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <NLPacketList.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingHelpers.h>
|
||||
#include <AvatarData.h> //for KillAvatarReason
|
||||
#include <FingerprintUtils.h>
|
||||
#include "DomainServerNodeData.h"
|
||||
|
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
|||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
||||
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
||||
_descriptionArray(),
|
||||
_configMap()
|
||||
{
|
||||
DomainServerSettingsManager::DomainServerSettingsManager() {
|
||||
// load the description object from the settings description
|
||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||
descriptionFile.open(QIODevice::ReadOnly);
|
||||
|
@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
|
|||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
_argumentList = argumentList;
|
||||
|
||||
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
_configMap.loadConfig(_argumentList);
|
||||
|
||||
static const auto VERSION_SETTINGS_KEYPATH = "version";
|
||||
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
|
||||
|
||||
if (!versionVariant) {
|
||||
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
|
||||
*versionVariant = _descriptionVersion;
|
||||
persistToFile();
|
||||
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
|
||||
}
|
||||
|
||||
{
|
||||
// Backward compatibility migration code
|
||||
// The config version used to be stored in a different file
|
||||
// This moves it to the actual config file.
|
||||
Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
||||
if (JSON_SETTING_VERSION.isSet()) {
|
||||
auto version = JSON_SETTING_VERSION.get();
|
||||
*versionVariant = version;
|
||||
persistToFile();
|
||||
QFile::remove(settingsFilename());
|
||||
}
|
||||
}
|
||||
|
||||
// What settings version were we before and what are we using now?
|
||||
// Do we need to do any re-mapping?
|
||||
double oldVersion = JSON_SETTING_VERSION.get();
|
||||
double oldVersion = versionVariant->toDouble();
|
||||
|
||||
if (oldVersion != _descriptionVersion) {
|
||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||
|
@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
|
||||
|
||||
*restrictedAccess = QVariant(true);
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so that the merged config is right
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
*entityServerVariant = entityServerMap;
|
||||
}
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so that the merged config is right
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
|
||||
|
||||
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
|
||||
// write the new settings to file
|
||||
persistToFile();
|
||||
|
||||
// reload the master and user config so the merged config is correct
|
||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
packPermissions();
|
||||
}
|
||||
|
||||
if (oldVersion < 1.7) {
|
||||
// This was prior to the removal of the master config file
|
||||
// So we write the merged config to the user config file, and stop reading from the user config file
|
||||
|
||||
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
|
||||
|
||||
// replace the user config by the merged config
|
||||
_configMap.getConfig() = _configMap.getMergedConfig();
|
||||
|
||||
// persist the new config so the user config file has the correctly merged config
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
if (oldVersion < 1.8) {
|
||||
unpackPermissions();
|
||||
// This was prior to addition of domain content replacement, add that to localhost permissions by default
|
||||
|
@ -310,12 +297,22 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities);
|
||||
packPermissions();
|
||||
}
|
||||
if (oldVersion < 2.0) {
|
||||
const QString WIZARD_COMPLETED_ONCE = "wizard.completed_once";
|
||||
|
||||
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
|
||||
|
||||
*wizardCompletedOnce = QVariant(true);
|
||||
}
|
||||
|
||||
// write the current description version to our settings
|
||||
*versionVariant = _descriptionVersion;
|
||||
|
||||
// write the new settings to the json file
|
||||
persistToFile();
|
||||
}
|
||||
|
||||
unpackPermissions();
|
||||
|
||||
// write the current description version to our settings
|
||||
JSON_SETTING_VERSION.set(_descriptionVersion);
|
||||
}
|
||||
|
||||
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
||||
|
@ -960,29 +957,6 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
|||
return QVariant();
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a GET operation for our settings
|
||||
|
||||
// check if there is a query parameter for settings affecting a particular type of assignment
|
||||
const QString SETTINGS_TYPE_QUERY_KEY = "type";
|
||||
QUrlQuery settingsQuery(url);
|
||||
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
|
||||
|
||||
if (!typeValue.isEmpty()) {
|
||||
QJsonObject responseObject = responseObjectForType(typeValue);
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a POST operation to change one or more settings
|
||||
|
@ -1214,6 +1188,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
static const QString SECURITY_ROOT_KEY = "security";
|
||||
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
|
||||
static const QString BROADCASTING_KEY = "broadcasting";
|
||||
static const QString WIZARD_KEY = "wizard";
|
||||
static const QString DESCRIPTION_ROOT_KEY = "descriptors";
|
||||
|
||||
auto& settingsVariant = _configMap.getConfig();
|
||||
|
@ -1266,7 +1241,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY ) {
|
||||
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
|
||||
needRestart = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -1282,8 +1258,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
|
||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY)
|
||||
|| settingKey == AC_SUBNET_WHITELIST_KEY) {
|
||||
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
|
||||
settingKey == AC_SUBNET_WHITELIST_KEY) {
|
||||
needRestart = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -1299,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
}
|
||||
}
|
||||
|
||||
// re-merge the user and master configs after a settings change
|
||||
_configMap.mergeMasterAndUserConfigs();
|
||||
|
||||
return needRestart;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ class DomainServerSettingsManager : public QObject {
|
|||
Q_OBJECT
|
||||
public:
|
||||
DomainServerSettingsManager();
|
||||
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
|
||||
void setupConfigMap(const QStringList& argumentList);
|
||||
|
|
|
@ -87,7 +87,7 @@ Window {
|
|||
onReceivedHifiSchemeURL: resetAfterTeleport();
|
||||
|
||||
// Update location after using back and forward buttons.
|
||||
onMetaverseServerUrlChanged: updateLocationTextTimer.start();
|
||||
onHostChanged: updateLocationTextTimer.start();
|
||||
|
||||
ListModel { id: suggestions }
|
||||
|
||||
|
|
|
@ -55,8 +55,21 @@ Item {
|
|||
mouse.accepted = true;
|
||||
}
|
||||
|
||||
property var _HAPTIC_STRENGTH: 0.1;
|
||||
property var _HAPTIC_DURATION: 3.0;
|
||||
property var leftHand: 0;
|
||||
property var rightHand: 1;
|
||||
|
||||
onEntered: {
|
||||
keyItem.state = "mouseOver";
|
||||
|
||||
var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY);
|
||||
var deviceId = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
|
||||
var hand = deviceId - 1; // based on touchEventUtils.js, deviceId is 'hand + 1', so 'hand' is 'deviceId' - 1
|
||||
|
||||
if (hand == leftHand || hand == rightHand) {
|
||||
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, hand);
|
||||
}
|
||||
}
|
||||
|
||||
onExited: {
|
||||
|
|
|
@ -33,7 +33,7 @@ Window {
|
|||
|
||||
BaseWebView {
|
||||
id: webview
|
||||
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
|
||||
url: Account.metaverseServerURL + "/marketplace?category=avatars"
|
||||
focus: true
|
||||
|
||||
anchors {
|
||||
|
|
|
@ -16,10 +16,10 @@ import Qt.labs.settings 1.0
|
|||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "../windows"
|
||||
import "../windows" as Windows
|
||||
import "../dialogs"
|
||||
|
||||
ScrollingWindow {
|
||||
Windows.ScrollingWindow {
|
||||
id: root
|
||||
objectName: "AssetServer"
|
||||
title: "Asset Browser"
|
||||
|
|
|
@ -17,7 +17,7 @@ import "../styles-uit"
|
|||
import "../controls-uit" as HifiControls
|
||||
import "toolbars"
|
||||
|
||||
// references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager from root context
|
||||
// references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager, Account from root context
|
||||
|
||||
Item {
|
||||
id: thisNameCard
|
||||
|
@ -30,7 +30,6 @@ Item {
|
|||
|
||||
// Properties
|
||||
property string profileUrl: "";
|
||||
property string defaultBaseUrl: AddressManager.metaverseServerUrl;
|
||||
property string connectionStatus : ""
|
||||
property string uuid: ""
|
||||
property string displayName: ""
|
||||
|
@ -59,7 +58,7 @@ Item {
|
|||
clip: true
|
||||
Image {
|
||||
id: userImage
|
||||
source: profileUrl !== "" ? ((0 === profileUrl.indexOf("http")) ? profileUrl : (defaultBaseUrl + profileUrl)) : "";
|
||||
source: profileUrl !== "" ? ((0 === profileUrl.indexOf("http")) ? profileUrl : (Account.metaverseServerURL + profileUrl)) : "";
|
||||
mipmap: true;
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
|
@ -95,7 +94,7 @@ Item {
|
|||
enabled: (selected && activeTab == "nearbyTab") || isMyCard;
|
||||
hoverEnabled: enabled
|
||||
onClicked: {
|
||||
userInfoViewer.url = defaultBaseUrl + "/users/" + userName;
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
}
|
||||
onEntered: infoHoverImage.visible = true;
|
||||
|
@ -366,7 +365,7 @@ Item {
|
|||
enabled: selected
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
userInfoViewer.url = defaultBaseUrl + "/users/" + userName;
|
||||
userInfoViewer.url = Account.metaverseServerURL + "/users/" + userName;
|
||||
userInfoViewer.visible = true;
|
||||
}
|
||||
onEntered: {
|
||||
|
@ -433,7 +432,8 @@ Item {
|
|||
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
|
||||
x: 240
|
||||
onClicked: {
|
||||
AddressManager.goToUser(thisNameCard.userName);
|
||||
console.log("Vist user button clicked."); // Remove after debugging.
|
||||
AddressManager.goToUser(thisNameCard.userName, false);
|
||||
UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
|
||||
}
|
||||
}
|
||||
|
@ -595,7 +595,10 @@ Item {
|
|||
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
|
||||
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
|
||||
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
|
||||
// Position avatar 2 metres from the target in the direction that target avatar was facing.
|
||||
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
|
||||
MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1});
|
||||
|
||||
// Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
|
||||
MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,6 @@ import "../controls" as HifiControls
|
|||
|
||||
Rectangle {
|
||||
id: pal;
|
||||
// Size
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
// Style
|
||||
color: "#E3E3E3";
|
||||
// Properties
|
||||
|
@ -830,7 +827,7 @@ Rectangle {
|
|||
hoverEnabled: enabled
|
||||
enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
|
||||
onClicked: {
|
||||
AddressManager.goToUser(model.userName);
|
||||
AddressManager.goToUser(model.userName, false);
|
||||
UserActivityLogger.palAction("go_to_user", model.userName);
|
||||
}
|
||||
onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;
|
||||
|
|
|
@ -213,8 +213,8 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
peak: model.peak;
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: (bar.currentIndex === 1 && selectedHMD && isVR) ||
|
||||
(bar.currentIndex === 0 && selectedDesktop && !isVR) &&
|
||||
visible: ((bar.currentIndex === 1 && isVR) ||
|
||||
(bar.currentIndex === 0 && !isVR)) &&
|
||||
Audio.devices.input.peakValuesAvailable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -585,7 +585,7 @@ Rectangle {
|
|||
// "Rez" button
|
||||
HifiControlsUit.Button {
|
||||
id: rezNowButton;
|
||||
enabled: root.canRezCertifiedItems;
|
||||
enabled: root.canRezCertifiedItems || root.isWearable;
|
||||
buttonGlyph: hifi.glyphs.lightning;
|
||||
color: hifi.buttons.red;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
|
@ -634,7 +634,7 @@ Rectangle {
|
|||
}
|
||||
RalewaySemiBold {
|
||||
id: explainRezText;
|
||||
//visible: !root.isWearable;
|
||||
visible: !root.isWearable;
|
||||
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
|
||||
// Text size
|
||||
size: 16;
|
||||
|
|
|
@ -25,7 +25,7 @@ Item {
|
|||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property string referrerURL: "https://metaverse.highfidelity.com/marketplace?";
|
||||
property string referrerURL: (Account.metaverseServerURL + "/marketplace?");
|
||||
readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin;
|
||||
property alias usernameDropdownVisible: usernameDropdown.visible;
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ Rectangle {
|
|||
property string dateOfPurchase: "--";
|
||||
property bool isLightbox: false;
|
||||
property bool isMyCert: false;
|
||||
property bool isCertificateInvalid: false;
|
||||
// Style
|
||||
color: hifi.colors.faintGray;
|
||||
Hifi.QmlCommerce {
|
||||
|
@ -44,10 +45,11 @@ Rectangle {
|
|||
} else {
|
||||
root.marketplaceUrl = result.data.marketplace_item_url;
|
||||
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
||||
root.itemOwner = root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
||||
root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run);
|
||||
root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000);
|
||||
root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
|
||||
root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" :
|
||||
(result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run));
|
||||
root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000);
|
||||
root.itemName = result.data.marketplace_item_name;
|
||||
|
||||
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
|
||||
|
@ -65,6 +67,44 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
onUpdateCertificateStatus: {
|
||||
if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||
// NOP
|
||||
} else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
|
||||
root.isCertificateInvalid = true;
|
||||
errorText.text = "Verification of this certificate timed out.";
|
||||
errorText.color = hifi.colors.redHighlight;
|
||||
} else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The avatar who rezzed this item doesn't own it.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else {
|
||||
console.log("Unknown certificate status received from ledger signal!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCertificateIdChanged: {
|
||||
|
@ -216,7 +256,7 @@ Rectangle {
|
|||
}
|
||||
AnonymousProRegular {
|
||||
id: isMyCertText;
|
||||
visible: root.isMyCert;
|
||||
visible: root.isMyCert && !root.isCertificateInvalid;
|
||||
text: "(Private)";
|
||||
size: 18;
|
||||
// Anchors
|
||||
|
|
|
@ -14,13 +14,25 @@ Item {
|
|||
readonly property var originalAttachments: MyAvatar.getAttachmentsVariant();
|
||||
property var attachments: [];
|
||||
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < originalAttachments.length; ++i) {
|
||||
var attachment = originalAttachments[i];
|
||||
function reload(){
|
||||
content.attachments = [];
|
||||
var currentAttachments = MyAvatar.getAttachmentsVariant();
|
||||
listView.model.clear();
|
||||
for (var i = 0; i < currentAttachments.length; ++i) {
|
||||
var attachment = currentAttachments[i];
|
||||
content.attachments.push(attachment);
|
||||
listView.model.append({});
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MyAvatar
|
||||
onAttachmentsChanged: reload()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
reload()
|
||||
}
|
||||
|
||||
Column {
|
||||
width: pane.width
|
||||
|
|
|
@ -134,7 +134,7 @@ StackView {
|
|||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
onMetaverseServerUrlChanged: updateLocationTextTimer.start();
|
||||
onHostChanged: updateLocationTextTimer.start();
|
||||
Rectangle {
|
||||
id: navBar
|
||||
width: parent.width
|
||||
|
|
|
@ -219,7 +219,7 @@ Item {
|
|||
|
||||
function setShown(value) {
|
||||
if (value === true) {
|
||||
HMD.openTablet()
|
||||
HMD.openTablet(HMD.tabletContextualMode) // pass in current contextual mode flag to maintain flag (otherwise uses default false argument)
|
||||
} else {
|
||||
HMD.closeTablet()
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ Item {
|
|||
|
||||
BaseWebView {
|
||||
id: webview
|
||||
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
|
||||
url: (Account.metaverseServerURL + "/marketplace?category=avatars")
|
||||
focus: true
|
||||
|
||||
anchors {
|
||||
|
|
|
@ -1018,6 +1018,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// connect to the packet sent signal of the _entityEditSender
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::addingEntityWithCertificate, this, &Application::addingEntityWithCertificate);
|
||||
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
|
||||
|
@ -2692,7 +2693,7 @@ bool Application::importFromZIP(const QString& filePath) {
|
|||
qDebug() << "A zip file has been dropped in: " << filePath;
|
||||
QUrl empty;
|
||||
// handle Blocks download from Marketplace
|
||||
if (filePath.contains("vr.google.com/downloads")) {
|
||||
if (filePath.contains("poly.google.com/downloads")) {
|
||||
addAssetToWorldFromURL(filePath);
|
||||
} else {
|
||||
qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false);
|
||||
|
@ -5739,6 +5740,11 @@ int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer
|
|||
void Application::packetSent(quint64 length) {
|
||||
}
|
||||
|
||||
void Application::addingEntityWithCertificate(const QString& certificateID, const QString& placeName) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
ledger->updateLocation(certificateID, placeName);
|
||||
}
|
||||
|
||||
void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) {
|
||||
|
||||
scriptEngine->setEmitScriptUpdatesFunction([this]() {
|
||||
|
@ -6229,7 +6235,7 @@ void Application::addAssetToWorldFromURL(QString url) {
|
|||
if (url.contains("filename")) {
|
||||
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
|
||||
}
|
||||
if (url.contains("vr.google.com/downloads")) {
|
||||
if (url.contains("poly.google.com/downloads")) {
|
||||
filename = url.section('/', -1);
|
||||
if (url.contains("noDownload")) {
|
||||
filename.remove(".zip?noDownload=false");
|
||||
|
@ -6264,7 +6270,7 @@ void Application::addAssetToWorldFromURLRequestFinished() {
|
|||
if (url.contains("filename")) {
|
||||
filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL.
|
||||
}
|
||||
if (url.contains("vr.google.com/downloads")) {
|
||||
if (url.contains("poly.google.com/downloads")) {
|
||||
filename = url.section('/', -1);
|
||||
if (url.contains("noDownload")) {
|
||||
filename.remove(".zip?noDownload=false");
|
||||
|
|
|
@ -428,6 +428,7 @@ private slots:
|
|||
void nodeActivated(SharedNodePointer node);
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
static void packetSent(quint64 length);
|
||||
static void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
|
||||
void updateDisplayMode();
|
||||
void domainConnectionRefused(const QString& reasonMessage, int reason, const QString& extraInfo);
|
||||
|
||||
|
|
|
@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
const AvatarPriority& sortData = sortedAvatars.top();
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
||||
|
||||
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||
if (ignoring) {
|
||||
sortedAvatars.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// for ALL avatars...
|
||||
if (_shouldRender) {
|
||||
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
||||
|
|
|
@ -1452,6 +1452,7 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
|
|||
return;
|
||||
}
|
||||
Avatar::setAttachmentData(attachmentData);
|
||||
emit attachmentsChanged();
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getSkeletonPosition() const {
|
||||
|
@ -2188,6 +2189,7 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) {
|
|||
|
||||
setTargetScale(clampedTargetScale);
|
||||
qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale);
|
||||
emit(scaleChanged());
|
||||
}
|
||||
|
||||
float MyAvatar::getDomainMinScale() {
|
||||
|
|
|
@ -619,6 +619,8 @@ signals:
|
|||
void skeletonChanged();
|
||||
void dominantHandChanged(const QString& hand);
|
||||
void sensorToWorldScaleChanged(float sensorToWorldScale);
|
||||
void attachmentsChanged();
|
||||
void scaleChanged();
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
|||
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
||||
}
|
||||
|
||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
if (!accountManager->isLoggedIn()) {
|
||||
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
||||
|
@ -99,7 +99,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
|||
return false; // We know right away that we will fail, so tell the caller.
|
||||
}
|
||||
|
||||
signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||
QJsonObject transaction;
|
||||
transaction["hfc_key"] = hfc_key;
|
||||
transaction["machine_fingerprint"] = machine_fingerprint;
|
||||
QJsonDocument transactionDoc{ transaction };
|
||||
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||
|
||||
signedSend("transaction", transactionString, old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
||||
}
|
||||
|
||||
|
@ -220,18 +226,26 @@ void Ledger::account() {
|
|||
}
|
||||
|
||||
// The api/failResponse is called just for the side effect of logging.
|
||||
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("reset", reply); }
|
||||
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("reset", reply); }
|
||||
void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); }
|
||||
void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); }
|
||||
void Ledger::updateLocation(const QString& asset_id, const QString location, const bool controlledFailure) {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
QStringList keys = wallet->listPublicKeys();
|
||||
QString key = keys[0];
|
||||
QJsonObject transaction;
|
||||
transaction["asset_id"] = asset_id;
|
||||
transaction["location"] = location;
|
||||
QJsonDocument transactionDoc{ transaction };
|
||||
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
|
||||
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
|
||||
uint walletStatus = walletScriptingInterface->getWalletStatus();
|
||||
|
||||
if (walletStatus != (uint)wallet->WALLET_STATUS_READY) {
|
||||
emit walletScriptingInterface->walletNotSetup();
|
||||
qDebug(commerce) << "User attempted to update the location of a certificate, but their wallet wasn't ready. Status:" << walletStatus;
|
||||
} else {
|
||||
QStringList keys = wallet->listPublicKeys();
|
||||
QString key = keys[0];
|
||||
QJsonObject transaction;
|
||||
transaction["certificate_id"] = asset_id;
|
||||
transaction["place_name"] = location;
|
||||
QJsonDocument transactionDoc{ transaction };
|
||||
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||
signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure);
|
||||
}
|
||||
}
|
||||
|
||||
void Ledger::certificateInfoSuccess(QNetworkReply& reply) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency {
|
|||
|
||||
public:
|
||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
||||
bool receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint);
|
||||
void balance(const QStringList& keys);
|
||||
void inventory(const QStringList& keys);
|
||||
void history(const QStringList& keys);
|
||||
|
@ -35,6 +35,14 @@ public:
|
|||
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
||||
void certificateInfo(const QString& certificateId);
|
||||
|
||||
enum CertificateStatus {
|
||||
CERTIFICATE_STATUS_UNKNOWN = 0,
|
||||
CERTIFICATE_STATUS_VERIFICATION_SUCCESS,
|
||||
CERTIFICATE_STATUS_VERIFICATION_TIMEOUT,
|
||||
CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED,
|
||||
CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED,
|
||||
};
|
||||
|
||||
signals:
|
||||
void buyResult(QJsonObject result);
|
||||
void receiveAtResult(QJsonObject result);
|
||||
|
@ -45,6 +53,8 @@ signals:
|
|||
void locationUpdateResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
public slots:
|
||||
void buySuccess(QNetworkReply& reply);
|
||||
void buyFailure(QNetworkReply& reply);
|
||||
|
|
|
@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
|||
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
|
||||
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
|
||||
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
|
||||
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
|
||||
setPassphrase("");
|
||||
});
|
||||
}
|
||||
|
||||
void QmlCommerce::getWalletStatus() {
|
||||
|
|
|
@ -45,6 +45,8 @@ signals:
|
|||
void accountResult(QJsonObject result);
|
||||
void certificateInfoResult(QJsonObject result);
|
||||
|
||||
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||
|
||||
protected:
|
||||
Q_INVOKABLE void getWalletStatus();
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "ui/ImageProvider.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
#include <FingerprintUtils.h>
|
||||
#include <PathUtils.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <AccountManager.h>
|
||||
|
@ -319,6 +320,7 @@ Wallet::Wallet() {
|
|||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
|
||||
|
||||
connect(ledger.data(), &Ledger::accountResult, this, [&]() {
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
|
@ -540,7 +542,8 @@ bool Wallet::generateKeyPair() {
|
|||
// 2. It is maximally private, and we can step back from that later if desired.
|
||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
return ledger->receiveAt(key, oldKey);
|
||||
QString machineFingerprint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
|
||||
return ledger->receiveAt(key, oldKey, machineFingerprint);
|
||||
}
|
||||
|
||||
QStringList Wallet::listPublicKeys() {
|
||||
|
@ -717,30 +720,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
|
|||
}
|
||||
|
||||
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
QString decryptedText;
|
||||
quint64 encryptedTextSize;
|
||||
quint64 publicKeySize;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (verifyOwnerChallenge(packet->read(packet->readPrimitive(&encryptedTextSize)), packet->read(packet->readPrimitive(&publicKeySize)), decryptedText)) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
// setup the packet
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, NUM_BYTES_RFC4122_UUID + decryptedText.size(), true);
|
||||
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||
unsigned char decryptedText[64];
|
||||
int certIDByteArraySize;
|
||||
int encryptedTextByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
// write the decrypted text to the packet
|
||||
decryptedTextPacket->write(decryptedText.toUtf8());
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&encryptedTextByteArraySize);
|
||||
if (challengeOriginatedFromClient) {
|
||||
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text";
|
||||
QByteArray certID = packet->read(certIDByteArraySize);
|
||||
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
|
||||
QByteArray challengingNodeUUID;
|
||||
if (challengeOriginatedFromClient) {
|
||||
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
|
||||
int decryptionStatus = -1;
|
||||
|
||||
if (rsa) {
|
||||
ERR_clear_error();
|
||||
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
|
||||
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
|
||||
decryptedText,
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
|
||||
RSA_free(rsa);
|
||||
} else {
|
||||
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
|
||||
}
|
||||
|
||||
QByteArray decryptedTextByteArray;
|
||||
if (decryptionStatus > -1) {
|
||||
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
|
||||
}
|
||||
int decryptedTextByteArraySize = decryptedTextByteArray.size();
|
||||
int certIDSize = certID.size();
|
||||
// setup the packet
|
||||
if (challengeOriginatedFromClient) {
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
|
||||
decryptedTextPacket->writePrimitive(certIDSize);
|
||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||
decryptedTextPacket->write(certID);
|
||||
decryptedTextPacket->write(decryptedTextByteArray);
|
||||
decryptedTextPacket->write(challengingNodeUUID);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||
|
||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||
} else {
|
||||
qCDebug(commerce) << "verifyOwnerChallenge() returned false";
|
||||
}
|
||||
}
|
||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
|
||||
|
||||
bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText) {
|
||||
// I have no idea how to do this yet, so here's some dummy code that may not even work.
|
||||
decryptedText = QString("hello");
|
||||
return true;
|
||||
decryptedTextPacket->writePrimitive(certIDSize);
|
||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||
decryptedTextPacket->write(certID);
|
||||
decryptedTextPacket->write(decryptedTextByteArray);
|
||||
|
||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||
|
||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||
}
|
||||
|
||||
if (decryptionStatus == -1) {
|
||||
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
|
||||
long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "RSA error:" << error_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Wallet::account() {
|
||||
|
|
|
@ -82,8 +82,6 @@ private:
|
|||
bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
|
||||
bool writeBackupInstructions();
|
||||
|
||||
bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText);
|
||||
|
||||
void account();
|
||||
};
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende
|
|||
_distanceScaleEnd(distanceScaleEnd),
|
||||
_rayPickUID(DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps))
|
||||
{
|
||||
|
||||
|
||||
for (auto& state : _renderStates) {
|
||||
if (!enabled || state.first != _currentRenderState) {
|
||||
|
@ -119,23 +118,25 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
|
|||
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
|
||||
}
|
||||
glm::vec3 endVec;
|
||||
if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) {
|
||||
if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) {
|
||||
endVec = pickRay.origin + pickRay.direction * distance;
|
||||
} else {
|
||||
if (!_objectLockEnd.first.isNull()) {
|
||||
if (!_lockEndObject.id.isNull()) {
|
||||
glm::vec3 pos;
|
||||
glm::quat rot;
|
||||
glm::vec3 dim;
|
||||
glm::vec3 registrationPoint;
|
||||
if (_objectLockEnd.second) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value);
|
||||
if (_lockEndObject.isOverlay) {
|
||||
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
|
||||
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
|
||||
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
|
||||
registrationPoint = glm::vec3(0.5f);
|
||||
} else {
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_objectLockEnd.first);
|
||||
pos = props.getPosition();
|
||||
rot = props.getRotation();
|
||||
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
|
||||
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
|
||||
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
|
||||
pos = extractTranslation(finalPosAndRotMat);
|
||||
rot = glmExtractRotation(finalPosAndRotMat);
|
||||
dim = props.getDimensions();
|
||||
registrationPoint = props.getRegistrationPoint();
|
||||
}
|
||||
|
@ -209,7 +210,7 @@ void LaserPointer::update() {
|
|||
withReadLock([&] {
|
||||
RayPickResult prevRayPickResult = qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID);
|
||||
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
|
||||
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
|
||||
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
|
||||
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance;
|
||||
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false);
|
||||
disableRenderState(_defaultRenderStates[_currentRenderState].second);
|
||||
|
@ -233,9 +234,11 @@ void LaserPointer::setLaserLength(const float laserLength) {
|
|||
});
|
||||
}
|
||||
|
||||
void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) {
|
||||
void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat) {
|
||||
withWriteLock([&] {
|
||||
_objectLockEnd = std::pair<QUuid, bool>(objectID, isOverlay);
|
||||
_lockEndObject.id = objectID;
|
||||
_lockEndObject.isOverlay = isOverlay;
|
||||
_lockEndObject.offsetMat = offsetMat;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
|
||||
class RayPickResult;
|
||||
|
||||
struct LockEndObject {
|
||||
QUuid id { QUuid() };
|
||||
bool isOverlay { false };
|
||||
glm::mat4 offsetMat { glm::mat4() };
|
||||
};
|
||||
|
||||
class RenderState {
|
||||
|
||||
public:
|
||||
|
@ -74,7 +80,7 @@ public:
|
|||
|
||||
void setPrecisionPicking(const bool precisionPicking);
|
||||
void setLaserLength(const float laserLength);
|
||||
void setLockEndUUID(QUuid objectID, const bool isOverlay);
|
||||
void setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4());
|
||||
|
||||
void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
|
||||
void setIncludeItems(const QVector<QUuid>& includeItems) const;
|
||||
|
@ -91,7 +97,7 @@ private:
|
|||
bool _centerEndY;
|
||||
bool _lockEnd;
|
||||
bool _distanceScaleEnd;
|
||||
std::pair<QUuid, bool> _objectLockEnd { std::pair<QUuid, bool>(QUuid(), false)};
|
||||
LockEndObject _lockEndObject;
|
||||
|
||||
const QUuid _rayPickUID;
|
||||
|
||||
|
|
|
@ -113,9 +113,9 @@ void LaserPointerManager::setIncludeItems(const QUuid& uid, const QVector<QUuid>
|
|||
}
|
||||
}
|
||||
|
||||
void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const {
|
||||
void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) const {
|
||||
auto laserPointer = find(uid);
|
||||
if (laserPointer) {
|
||||
laserPointer->setLockEndUUID(objectID, isOverlay);
|
||||
laserPointer->setLockEndUUID(objectID, isOverlay, offsetMat);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const;
|
||||
void setIncludeItems(const QUuid& uid, const QVector<QUuid>& includeEntities) const;
|
||||
|
||||
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const;
|
||||
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const;
|
||||
|
||||
void update();
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public slots:
|
|||
Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities) const;
|
||||
Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities) const;
|
||||
|
||||
Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); }
|
||||
Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
|
||||
|
||||
private:
|
||||
static RenderState buildRenderState(const QVariantMap& propMap);
|
||||
|
|
|
@ -25,6 +25,11 @@ class AccountScriptingInterface : public QObject {
|
|||
* @property username {String} username if user is logged in, otherwise it returns "Unknown user"
|
||||
*/
|
||||
|
||||
public:
|
||||
|
||||
Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
|
||||
QUrl getMetaverseServerURL() { return DependencyManager::get<AccountManager>()->getMetaverseServerURL(); }
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
|
|
|
@ -100,10 +100,21 @@ void HMDScriptingInterface::deactivateHMDHandMouse() {
|
|||
|
||||
void HMDScriptingInterface::closeTablet() {
|
||||
_showTablet = false;
|
||||
_tabletContextualMode = false;
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::openTablet() {
|
||||
void HMDScriptingInterface::openTablet(bool contextualMode) {
|
||||
_showTablet = true;
|
||||
_tabletContextualMode = contextualMode;
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::toggleShouldShowTablet() {
|
||||
setShouldShowTablet(!getShouldShowTablet());
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::setShouldShowTablet(bool value) {
|
||||
_showTablet = value;
|
||||
_tabletContextualMode = false;
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
|
|
|
@ -30,6 +30,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
Q_PROPERTY(glm::quat orientation READ getOrientation)
|
||||
Q_PROPERTY(bool mounted READ isMounted NOTIFY mountedChanged)
|
||||
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
|
||||
Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode)
|
||||
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
|
||||
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
|
||||
Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHightlightID WRITE setCurrentHomeButtonHightlightID)
|
||||
|
@ -75,7 +76,7 @@ public:
|
|||
|
||||
Q_INVOKABLE void closeTablet();
|
||||
|
||||
Q_INVOKABLE void openTablet();
|
||||
Q_INVOKABLE void openTablet(bool contextualMode = false);
|
||||
|
||||
signals:
|
||||
bool shouldShowHandControllersChanged();
|
||||
|
@ -88,9 +89,10 @@ public:
|
|||
|
||||
bool isMounted() const;
|
||||
|
||||
void toggleShouldShowTablet() { _showTablet = !_showTablet; }
|
||||
void setShouldShowTablet(bool value) { _showTablet = value; }
|
||||
void toggleShouldShowTablet();
|
||||
void setShouldShowTablet(bool value);
|
||||
bool getShouldShowTablet() const { return _showTablet; }
|
||||
bool getTabletContextualMode() const { return _tabletContextualMode; }
|
||||
|
||||
void setCurrentTabletFrameID(QUuid tabletID) { _tabletUIID = tabletID; }
|
||||
QUuid getCurrentTabletFrameID() const { return _tabletUIID; }
|
||||
|
@ -106,6 +108,7 @@ public:
|
|||
|
||||
private:
|
||||
bool _showTablet { false };
|
||||
bool _tabletContextualMode { false };
|
||||
QUuid _tabletUIID; // this is the entityID of the tablet frame
|
||||
QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui.
|
||||
QUuid _homeButtonID;
|
||||
|
|
|
@ -39,7 +39,7 @@ AddressBarDialog::AddressBarDialog(QQuickItem* parent) : OffscreenQmlDialog(pare
|
|||
});
|
||||
_backEnabled = !(DependencyManager::get<AddressManager>()->getBackStack().isEmpty());
|
||||
_forwardEnabled = !(DependencyManager::get<AddressManager>()->getForwardStack().isEmpty());
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &AddressBarDialog::metaverseServerUrlChanged);
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &AddressBarDialog::hostChanged);
|
||||
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::setUseFeed, this, &AddressBarDialog::setUseFeed);
|
||||
connect(qApp, &Application::receivedHifiSchemeURL, this, &AddressBarDialog::receivedHifiSchemeURL);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class AddressBarDialog : public OffscreenQmlDialog {
|
|||
Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged)
|
||||
Q_PROPERTY(bool forwardEnabled READ forwardEnabled NOTIFY forwardEnabledChanged)
|
||||
Q_PROPERTY(bool useFeed READ useFeed WRITE setUseFeed NOTIFY useFeedChanged)
|
||||
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl NOTIFY metaverseServerUrlChanged)
|
||||
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl)
|
||||
|
||||
public:
|
||||
AddressBarDialog(QQuickItem* parent = nullptr);
|
||||
|
@ -37,7 +37,7 @@ signals:
|
|||
void forwardEnabledChanged();
|
||||
void useFeedChanged();
|
||||
void receivedHifiSchemeURL(const QString& url);
|
||||
void metaverseServerUrlChanged();
|
||||
void hostChanged();
|
||||
|
||||
protected:
|
||||
void displayAddressOfflineMessage();
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <NetworkingConstants.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/SteamClientPlugin.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <AudioClient.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <devices/DdeFaceTracker.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <Preferences.h>
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <commerce/Ledger.h>
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
@ -42,6 +46,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
|||
_entityPropertyFlags += PROP_DIMENSIONS;
|
||||
_entityPropertyFlags += PROP_REGISTRATION_POINT;
|
||||
_entityPropertyFlags += PROP_CERTIFICATE_ID;
|
||||
_entityPropertyFlags += PROP_CLIENT_ONLY;
|
||||
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
|
||||
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
|
||||
|
@ -66,6 +72,11 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
|||
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
|
||||
|
||||
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
|
||||
_challengeOwnershipTimeoutTimer.setSingleShot(true);
|
||||
}
|
||||
|
||||
static const uint32_t MOUSE_HW_ID = 0;
|
||||
|
@ -260,6 +271,89 @@ void ContextOverlayInterface::openInspectionCertificate() {
|
|||
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
||||
_hmdScriptingInterface->openTablet();
|
||||
|
||||
setLastInspectedEntity(_currentEntityWithContextOverlay);
|
||||
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (entityProperties.getClientOnly()) {
|
||||
if (entityProperties.verifyStaticCertificateProperties()) {
|
||||
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||
|
||||
if (entityServer) {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = entityProperties.getCertificateID();
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
|
||||
qCDebug(entities) << "invalid_reason not empty";
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
|
||||
qCDebug(entities) << "'transfer_status' is 'failed'";
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
|
||||
qCDebug(entities) << "'transfer_status' is 'pending'";
|
||||
} else {
|
||||
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
|
||||
|
||||
QByteArray certID = entityProperties.getCertificateID().toUtf8();
|
||||
QByteArray encryptedText = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeEncryptedNonce(certID, ownerKey);
|
||||
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
|
||||
|
||||
int certIDByteArraySize = certID.length();
|
||||
int encryptedTextByteArraySize = encryptedText.length();
|
||||
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||
certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
|
||||
challengeOwnershipPacket->write(certID);
|
||||
challengeOwnershipPacket->write(encryptedText);
|
||||
challengeOwnershipPacket->write(nodeToChallengeByteArray);
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
|
||||
|
||||
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
|
||||
return;
|
||||
} else {
|
||||
startChallengeOwnershipTimer();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() <<
|
||||
"More info:" << networkReply->readAll();
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
} else {
|
||||
qCWarning(context_overlay) << "Couldn't get Entity Server!";
|
||||
}
|
||||
} else {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,3 +387,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
|
|||
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
|
||||
}
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::startChallengeOwnershipTimer() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
|
||||
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||
});
|
||||
|
||||
_challengeOwnershipTimeoutTimer.start(5000);
|
||||
}
|
||||
|
||||
void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
|
||||
int certIDByteArraySize;
|
||||
int decryptedTextByteArraySize;
|
||||
|
||||
packet->readPrimitive(&certIDByteArraySize);
|
||||
packet->readPrimitive(&decryptedTextByteArraySize);
|
||||
|
||||
QString certID(packet->read(certIDByteArraySize));
|
||||
QString decryptedText(packet->read(decryptedTextByteArraySize));
|
||||
|
||||
EntityItemID id;
|
||||
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyDecryptedNonce(certID, decryptedText, id);
|
||||
|
||||
if (verificationSuccess) {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
} else {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
|
||||
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
||||
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
||||
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
|
||||
void setEnabled(bool enabled);
|
||||
bool getEnabled() { return _enabled; }
|
||||
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
||||
|
@ -70,10 +71,14 @@ public slots:
|
|||
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
|
||||
bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
|
||||
|
||||
private slots:
|
||||
void handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
bool _verboseLogging { true };
|
||||
bool _enabled { true };
|
||||
QUuid _currentEntityWithContextOverlay{};
|
||||
EntityItemID _currentEntityWithContextOverlay{};
|
||||
EntityItemID _lastInspectedEntity{};
|
||||
QString _entityMarketplaceID;
|
||||
bool _contextOverlayJustClicked { false };
|
||||
|
||||
|
@ -87,6 +92,9 @@ private:
|
|||
void deletingEntity(const EntityItemID& entityItemID);
|
||||
|
||||
SelectionToSceneHandler _selectionToSceneHandler;
|
||||
|
||||
Q_INVOKABLE void startChallengeOwnershipTimer();
|
||||
QTimer _challengeOwnershipTimeoutTimer;
|
||||
};
|
||||
|
||||
#endif // hifi_ContextOverlayInterface_h
|
||||
|
|
|
@ -222,6 +222,9 @@ void Web3DOverlay::setupQmlSurface() {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this);
|
||||
|
||||
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
|
||||
|
||||
|
@ -250,6 +253,20 @@ void Web3DOverlay::onResizeWebSurface() {
|
|||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
}
|
||||
|
||||
const int INVALID_DEVICE_ID = -1;
|
||||
|
||||
Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
|
||||
auto mapped = _webSurface->getRootItem()->mapFromGlobal(QPoint(x, y));
|
||||
|
||||
for (auto pair : _activeTouchPoints) {
|
||||
if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) {
|
||||
return pair.first;
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_DEVICE_ID;
|
||||
}
|
||||
|
||||
void Web3DOverlay::render(RenderArgs* args) {
|
||||
if (!_visible || !getParentVisible()) {
|
||||
return;
|
||||
|
|
|
@ -67,6 +67,8 @@ public:
|
|||
void destroyWebSurface();
|
||||
void onResizeWebSurface();
|
||||
|
||||
Q_INVOKABLE int deviceIdByTouchPoint(qreal x, qreal y);
|
||||
|
||||
public slots:
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
|
||||
|
|
|
@ -152,6 +152,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f
|
|||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
|
||||
auto clipNode = std::make_shared<AnimClip>(role, url, firstFrame, lastFrame, timeScale, loop, false);
|
||||
_roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame };
|
||||
AnimNode::Pointer parent = node->getParent();
|
||||
parent->replaceChild(node, clipNode);
|
||||
} else {
|
||||
|
@ -1638,6 +1639,11 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
_userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
|
||||
overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
|
||||
}
|
||||
// restore the role animations we had before reset.
|
||||
for (auto& roleAnimState : _roleAnimStates) {
|
||||
auto roleState = roleAnimState.second;
|
||||
overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame);
|
||||
}
|
||||
_animLoading = false;
|
||||
|
||||
emit onLoadComplete();
|
||||
|
|
|
@ -335,8 +335,22 @@ protected:
|
|||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
|
||||
struct RoleAnimState {
|
||||
RoleAnimState() {}
|
||||
RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
|
||||
role(roleId), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}
|
||||
|
||||
QString role;
|
||||
QString url;
|
||||
float fps;
|
||||
bool loop;
|
||||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
|
||||
UserAnimState _userAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
float _rightHandOverlayAlpha { 0.0f };
|
||||
|
|
|
@ -286,6 +286,13 @@ void Avatar::updateAvatarEntities() {
|
|||
properties.setScript(noScript);
|
||||
}
|
||||
|
||||
auto specifiedHref = properties.getHref();
|
||||
if (!isMyAvatar() && !specifiedHref.isEmpty()) {
|
||||
qCDebug(avatars_renderer) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref;
|
||||
QString noHref;
|
||||
properties.setHref(noHref);
|
||||
}
|
||||
|
||||
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
|
||||
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
|
||||
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
|
||||
|
@ -721,19 +728,19 @@ void Avatar::simulateAttachments(float deltaTime) {
|
|||
glm::quat jointRotation;
|
||||
if (attachment.isSoft) {
|
||||
// soft attachments do not have transform offsets
|
||||
model->setTranslation(getPosition());
|
||||
model->setRotation(getOrientation() * Quaternions::Y_180);
|
||||
model->setTransformNoUpdateRenderItems(Transform(getOrientation() * Quaternions::Y_180, glm::vec3(1.0), getPosition()));
|
||||
model->simulate(deltaTime);
|
||||
model->updateRenderItems();
|
||||
} else {
|
||||
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
||||
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * getModelScale());
|
||||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setTransformNoUpdateRenderItems(Transform(jointRotation * attachment.rotation, glm::vec3(1.0), jointPosition + jointRotation * attachment.translation * getModelScale()));
|
||||
float scale = getModelScale() * attachment.scale;
|
||||
model->setScaleToFit(true, model->getNaturalDimensions() * scale, true); // hack to force rescale
|
||||
model->setSnapModelToCenter(false); // hack to force resnap
|
||||
model->setSnapModelToCenter(true);
|
||||
model->simulate(deltaTime);
|
||||
model->updateRenderItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2223,11 +2223,13 @@ glm::vec3 variantToVec3(const QVariant& var) {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AttachmentData::fromVariant(const QVariant& variant) {
|
||||
bool AttachmentData::fromVariant(const QVariant& variant) {
|
||||
bool isValid = false;
|
||||
auto map = variant.toMap();
|
||||
if (map.contains("modelUrl")) {
|
||||
auto urlString = map["modelUrl"].toString();
|
||||
modelURL = urlString;
|
||||
isValid = true;
|
||||
}
|
||||
if (map.contains("jointName")) {
|
||||
jointName = map["jointName"].toString();
|
||||
|
@ -2244,6 +2246,7 @@ void AttachmentData::fromVariant(const QVariant& variant) {
|
|||
if (map.contains("soft")) {
|
||||
isSoft = map["soft"].toBool();
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
QVariantList AvatarData::getAttachmentsVariant() const {
|
||||
|
@ -2259,8 +2262,7 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
|
|||
newAttachments.reserve(variant.size());
|
||||
for (const auto& attachmentVar : variant) {
|
||||
AttachmentData attachment;
|
||||
attachment.fromVariant(attachmentVar);
|
||||
if (!attachment.modelURL.isEmpty()) {
|
||||
if (attachment.fromVariant(attachmentVar)) {
|
||||
newAttachments.append(attachment);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -903,7 +903,7 @@ public:
|
|||
void fromJson(const QJsonObject& json);
|
||||
|
||||
QVariant toVariant() const;
|
||||
void fromVariant(const QVariant& variant);
|
||||
bool fromVariant(const QVariant& variant);
|
||||
};
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment);
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,10 @@ public:
|
|||
/// Parses the request content as form data, returning a list of header/content pairs.
|
||||
QList<FormData> parseFormData () const;
|
||||
|
||||
/// Parses the request content as a url encoded form, returning a hash of key/value pairs.
|
||||
/// Duplicate keys are not supported.
|
||||
QHash<QString, QString> parseUrlEncodedForm();
|
||||
|
||||
/// Sends a response and closes the connection.
|
||||
void respond (const char* code, const QByteArray& content = QByteArray(),
|
||||
const char* contentType = DefaultContentType,
|
||||
|
|
|
@ -100,7 +100,11 @@ EntityRendererPointer EntityTreeRenderer::renderableForEntityId(const EntityItem
|
|||
|
||||
render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& id) const {
|
||||
auto renderable = renderableForEntityId(id);
|
||||
return renderable ? renderable->getRenderItemID() : render::Item::INVALID_ITEM_ID;
|
||||
if (renderable) {
|
||||
return renderable->getRenderItemID();
|
||||
} else {
|
||||
return render::Item::INVALID_ITEM_ID;
|
||||
}
|
||||
}
|
||||
|
||||
int EntityTreeRenderer::_entitiesScriptEngineCount = 0;
|
||||
|
|
|
@ -295,6 +295,14 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans
|
|||
});
|
||||
}
|
||||
|
||||
void EntityRenderer::clearSubRenderItemIDs() {
|
||||
_subRenderItemIDs.clear();
|
||||
}
|
||||
|
||||
void EntityRenderer::setSubRenderItemIDs(const render::ItemIDs& ids) {
|
||||
_subRenderItemIDs = ids;
|
||||
}
|
||||
|
||||
//
|
||||
// Internal methods
|
||||
//
|
||||
|
|
|
@ -49,6 +49,9 @@ public:
|
|||
virtual bool addToScene(const ScenePointer& scene, Transaction& transaction) final;
|
||||
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction);
|
||||
|
||||
void clearSubRenderItemIDs();
|
||||
void setSubRenderItemIDs(const render::ItemIDs& ids);
|
||||
|
||||
protected:
|
||||
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
|
||||
virtual void onAddToScene(const EntityItemPointer& entity);
|
||||
|
@ -113,6 +116,7 @@ protected:
|
|||
SharedSoundPointer _collisionSound;
|
||||
QUuid _changeHandlerId;
|
||||
ItemID _renderItemID{ Item::INVALID_ITEM_ID };
|
||||
ItemIDs _subRenderItemIDs;
|
||||
quint64 _fadeStartTime{ usecTimestampNow() };
|
||||
bool _isFading{ _entitiesShouldFadeFunction() };
|
||||
bool _prevIsTransparent { false };
|
||||
|
|
|
@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() {
|
|||
model->setScaleToFit(true, getDimensions());
|
||||
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
|
||||
updateRenderItems = true;
|
||||
model->scaleToFit();
|
||||
}
|
||||
|
||||
bool success;
|
||||
|
@ -952,7 +953,7 @@ ItemKey ModelEntityRenderer::getKey() {
|
|||
|
||||
uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) {
|
||||
if (_model) {
|
||||
auto metaSubItems = _model->fetchRenderItemIDs();
|
||||
auto metaSubItems = _subRenderItemIDs;
|
||||
subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end());
|
||||
return (uint32_t)metaSubItems.size();
|
||||
}
|
||||
|
@ -1231,6 +1232,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
if ((bool)model) {
|
||||
model->removeFromScene(scene, transaction);
|
||||
withWriteLock([&] { _model.reset(); });
|
||||
transaction.updateItem<PayloadProxyInterface>(getRenderItemID(), [](PayloadProxyInterface& data) {
|
||||
auto entityRenderer = static_cast<EntityRenderer*>(&data);
|
||||
entityRenderer->clearSubRenderItemIDs();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1326,6 +1331,12 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
render::Item::Status::Getters statusGetters;
|
||||
makeStatusGetters(entity, statusGetters);
|
||||
model->addToScene(scene, transaction, statusGetters);
|
||||
|
||||
auto newRenderItemIDs{ model->fetchRenderItemIDs() };
|
||||
transaction.updateItem<PayloadProxyInterface>(getRenderItemID(), [newRenderItemIDs](PayloadProxyInterface& data) {
|
||||
auto entityRenderer = static_cast<EntityRenderer*>(&data);
|
||||
entityRenderer->setSubRenderItemIDs(newRenderItemIDs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -342,23 +342,23 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity)
|
|||
haze->setHazeActive(hazeMode == COMPONENT_MODE_ENABLED);
|
||||
haze->setAltitudeBased(_hazeProperties.getHazeAltitudeEffect());
|
||||
|
||||
haze->setHazeRangeFactor(model::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeRange()));
|
||||
haze->setHazeRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeRange()));
|
||||
xColor hazeColor = _hazeProperties.getHazeColor();
|
||||
haze->setHazeColor(glm::vec3(hazeColor.red / 255.0, hazeColor.green / 255.0, hazeColor.blue / 255.0));
|
||||
xColor hazeGlareColor = _hazeProperties.getHazeGlareColor();
|
||||
haze->setDirectionalLightColor(glm::vec3(hazeGlareColor.red / 255.0, hazeGlareColor.green / 255.0, hazeGlareColor.blue / 255.0));
|
||||
haze->setHazeGlareColor(glm::vec3(hazeGlareColor.red / 255.0, hazeGlareColor.green / 255.0, hazeGlareColor.blue / 255.0));
|
||||
haze->setHazeEnableGlare(_hazeProperties.getHazeEnableGlare());
|
||||
haze->setDirectionalLightBlend(model::convertDirectionalLightAngleToPower(_hazeProperties.getHazeGlareAngle()));
|
||||
haze->setHazeGlareBlend(model::Haze::convertGlareAngleToPower(_hazeProperties.getHazeGlareAngle()));
|
||||
|
||||
float hazeAltitude = _hazeProperties.getHazeCeiling() - _hazeProperties.getHazeBaseRef();
|
||||
haze->setHazeAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(hazeAltitude));
|
||||
haze->setHazeAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(hazeAltitude));
|
||||
haze->setHazeBaseReference(_hazeProperties.getHazeBaseRef());
|
||||
|
||||
haze->setHazeBackgroundBlendValue(_hazeProperties.getHazeBackgroundBlend());
|
||||
haze->setHazeBackgroundBlend(_hazeProperties.getHazeBackgroundBlend());
|
||||
|
||||
haze->setHazeAttenuateKeyLight(_hazeProperties.getHazeAttenuateKeyLight());
|
||||
haze->setHazeKeyLightRangeFactor(model::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange()));
|
||||
haze->setHazeKeyLightAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude()));
|
||||
haze->setHazeKeyLightRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange()));
|
||||
haze->setHazeKeyLightAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude()));
|
||||
|
||||
haze->setZoneTransform(entity->getTransform().getMatrix());
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
set(TARGET_NAME entities)
|
||||
setup_hifi_library(Network Script)
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
link_hifi_libraries(shared networking octree avatars)
|
||||
link_hifi_libraries(shared networking octree avatars model)
|
||||
|
|
|
@ -142,7 +142,8 @@ DiffTraversal::DiffTraversal() {
|
|||
_path.reserve(MIN_PATH_DEPTH);
|
||||
}
|
||||
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root,
|
||||
int32_t lodLevelOffset, bool usesViewFrustum) {
|
||||
assert(root);
|
||||
// there are three types of traversal:
|
||||
//
|
||||
|
|
|
@ -57,7 +57,8 @@ public:
|
|||
|
||||
DiffTraversal();
|
||||
|
||||
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
|
||||
Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
|
||||
bool usesViewFrustum);
|
||||
|
||||
const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; }
|
||||
const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; }
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "EntitiesLogging.h"
|
||||
#include "EntityItem.h"
|
||||
#include "EntityItemProperties.h"
|
||||
#include <AddressManager.h>
|
||||
|
||||
EntityEditPacketSender::EntityEditPacketSender() {
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
@ -92,24 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
|
||||
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
||||
|
||||
bool success;
|
||||
OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
EntityPropertyFlags didntFitProperties;
|
||||
EntityItemProperties propertiesCopy = properties;
|
||||
|
||||
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
|
||||
EntityItemProperties propertiesCopy = properties;
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
propertiesCopy.setParentID(myNodeID);
|
||||
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
|
||||
} else {
|
||||
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut);
|
||||
}
|
||||
|
||||
if (success) {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
||||
qCDebug(entities) << " id:" << entityItemID;
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
#endif
|
||||
queueOctreeEditMessage(type, bufferOut);
|
||||
EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
|
||||
|
||||
while (encodeResult == OctreeElement::PARTIAL) {
|
||||
encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
|
||||
|
||||
if (encodeResult != OctreeElement::NONE) {
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
||||
qCDebug(entities) << " id:" << entityItemID;
|
||||
qCDebug(entities) << " properties:" << properties;
|
||||
#endif
|
||||
queueOctreeEditMessage(type, bufferOut);
|
||||
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
|
||||
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
|
||||
}
|
||||
}
|
||||
|
||||
// if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit
|
||||
if (encodeResult != OctreeElement::COMPLETED) {
|
||||
type = PacketType::EntityEdit;
|
||||
requestedProperties = didntFitProperties;
|
||||
}
|
||||
|
||||
bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ public:
|
|||
virtual char getMyNodeType() const override { return NodeType::EntityServer; }
|
||||
virtual void adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, qint64 clockSkew) override;
|
||||
|
||||
signals:
|
||||
void addingEntityWithCertificate(const QString& certificateID, const QString& placeName);
|
||||
|
||||
public slots:
|
||||
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtEndian>
|
||||
#include <QJsonDocument>
|
||||
#include <openssl/rsa.h> // see comments for DEBUG_CERT
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
|
@ -41,6 +42,7 @@ int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
|||
|
||||
int EntityItem::_maxActionsDataSize = 800;
|
||||
quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;
|
||||
QString EntityItem::_marketplacePublicKey;
|
||||
|
||||
EntityItem::EntityItem(const EntityItemID& entityItemID) :
|
||||
SpatiallyNestable(NestableType::Entity, entityItemID)
|
||||
|
@ -81,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_ANGULAR_VELOCITY;
|
||||
requestedProperties += PROP_ACCELERATION;
|
||||
|
||||
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
|
||||
requestedProperties += PROP_DIMENSIONS;
|
||||
requestedProperties += PROP_DENSITY;
|
||||
requestedProperties += PROP_GRAVITY;
|
||||
requestedProperties += PROP_DAMPING;
|
||||
|
@ -239,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
|
||||
|
@ -1569,107 +1571,6 @@ float EntityItem::getRadius() const {
|
|||
return 0.5f * glm::length(getDimensions());
|
||||
}
|
||||
|
||||
// Checking Certifiable Properties
|
||||
#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N()
|
||||
#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString()
|
||||
#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N())
|
||||
QByteArray EntityItem::getStaticCertificateJSON() const {
|
||||
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
|
||||
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
|
||||
// of the entity as reviewed during the certification submission.
|
||||
|
||||
QJsonObject json;
|
||||
EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"!
|
||||
// It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically
|
||||
// to help maintainence in two different code bases.
|
||||
if (!propertySet.getAnimation().getURL().isEmpty()) {
|
||||
json["animation.url"] = propertySet.getAnimation().getURL();
|
||||
}
|
||||
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
||||
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
||||
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
||||
ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber);
|
||||
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
|
||||
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
|
||||
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
|
||||
ADD_STRING_PROPERTY(itemLicense, ItemLicense);
|
||||
ADD_STRING_PROPERTY(itemName, ItemName);
|
||||
ADD_INT_PROPERTY(limitedRun, LimitedRun);
|
||||
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
|
||||
ADD_STRING_PROPERTY(modelURL, ModelURL);
|
||||
ADD_STRING_PROPERTY(script, Script);
|
||||
ADD_ENUM_PROPERTY(shapeType, ShapeType);
|
||||
json["type"] = EntityTypes::getEntityTypeName(propertySet.getType());
|
||||
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
QByteArray EntityItem::getStaticCertificateHash() const {
|
||||
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_CERT
|
||||
QString EntityItem::computeCertificateID() {
|
||||
// Until the marketplace generates it, compute and answer the certificateID here.
|
||||
// Does not set it, as that will have to be done from script engine in order to update server, etc.
|
||||
const auto hash = getStaticCertificateHash();
|
||||
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
|
||||
const unsigned int textLength = hash.length();
|
||||
|
||||
const char privateKey[] = "-----BEGIN RSA PRIVATE KEY-----\n\
|
||||
MIIBOQIBAAJBALCoBiDAZOClO26tC5pd7JikBL61WIgpAqbcNnrV/TcG6LPI7Zbi\n\
|
||||
MjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQJABvOlwhYwIhL+gr12jm2R\n\
|
||||
yPPzZ9nVEQ6kFxLlZfIT09119fd6OU1X5d4sHWfMfSIEgjwQIDS3ZU1kY3XKo87X\n\
|
||||
zQIhAOPHlYa1OC7BLhaTouy68qIU2vCKLP8mt4S31/TT0UOnAiEAxor6gU6yupTQ\n\
|
||||
yuyV3yHvr5LkZKBGqhjmOTmDfgtX7ncCIChGbgX3nQuHVOLhD/nTxHssPNozVGl5\n\
|
||||
KxHof+LmYSYZAiB4U+yEh9SsXdq40W/3fpLMPuNq1PRezJ5jGidGMcvF+wIgUNec\n\
|
||||
3Kg2U+CVZr8/bDT/vXRrsKj1zfobYuvbfVH02QY=\n\
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
BIO* bio = BIO_new_mem_buf((void*)privateKey, sizeof(privateKey));
|
||||
RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
|
||||
|
||||
QByteArray signature(RSA_size(rsa), 0);
|
||||
unsigned int signatureLength = 0;
|
||||
const int signOK = RSA_sign(NID_sha256, text, textLength, reinterpret_cast<unsigned char*>(signature.data()), &signatureLength, rsa);
|
||||
BIO_free(bio);
|
||||
RSA_free(rsa);
|
||||
if (!signOK) {
|
||||
qCWarning(entities) << "Unable to compute signature for" << getName() << getEntityItemID();
|
||||
return "";
|
||||
}
|
||||
return signature.toBase64();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool EntityItem::verifyStaticCertificateProperties() {
|
||||
// True IIF a non-empty certificateID matches the static certificate json.
|
||||
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
|
||||
|
||||
if (getCertificateID().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const auto signatureBytes = QByteArray::fromBase64(getCertificateID().toLatin1());
|
||||
const auto signature = reinterpret_cast<const unsigned char*>(signatureBytes.constData());
|
||||
const unsigned int signatureLength = signatureBytes.length();
|
||||
|
||||
const auto hash = getStaticCertificateHash();
|
||||
const auto text = reinterpret_cast<const unsigned char*>(hash.constData());
|
||||
const unsigned int textLength = hash.length();
|
||||
|
||||
// After DEBUG_CERT ends, we will get/cache this once from the marketplace when needed, and it likely won't be RSA.
|
||||
const char publicKey[] = "-----BEGIN PUBLIC KEY-----\n\
|
||||
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALCoBiDAZOClO26tC5pd7JikBL61WIgp\n\
|
||||
AqbcNnrV/TcG6LPI7ZbiMjdUixmTNvYMRZH3Wlqtl2IKG1W68y3stKECAwEAAQ==\n\
|
||||
-----END PUBLIC KEY-----";
|
||||
BIO *bio = BIO_new_mem_buf((void*)publicKey, sizeof(publicKey));
|
||||
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
||||
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
|
||||
bool answer = RSA_verify(NID_sha256, text, textLength, signature, signatureLength, rsa);
|
||||
BIO_free(bio);
|
||||
RSA_free(rsa);
|
||||
EVP_PKEY_free(evp_key);
|
||||
return answer;
|
||||
}
|
||||
|
||||
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
|
||||
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
|
||||
glm::mat4 scale = glm::scale(getDimensions());
|
||||
|
@ -1754,7 +1655,6 @@ void EntityItem::updateDimensions(const glm::vec3& value) {
|
|||
setDimensions(value);
|
||||
markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
_queryAACubeSet = false;
|
||||
dimensionsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2986,3 +2886,34 @@ void EntityItem::somethingChangedNotification() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::retrieveMarketplacePublicKey() {
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
requestURL.setPath("/api/v1/commerce/marketplace_key");
|
||||
QJsonObject request;
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (!jsonObject["public_key"].toString().isEmpty()) {
|
||||
EntityItem::_marketplacePublicKey = jsonObject["public_key"].toString();
|
||||
qCWarning(entities) << "Marketplace public key has been set to" << _marketplacePublicKey;
|
||||
} else {
|
||||
qCWarning(entities) << "Marketplace public key is empty!";
|
||||
}
|
||||
} else {
|
||||
qCWarning(entities) << "Call to" << networkRequest.url() << "failed! Error:" << networkReply->error();
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,9 +36,6 @@
|
|||
#include "SimulationFlags.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
// FIXME: The server-side marketplace will soon create the certificateID. At that point, all of the DEBUG_CERT stuff will go away.
|
||||
#define DEBUG_CERT 1
|
||||
|
||||
class EntitySimulation;
|
||||
class EntityTreeElement;
|
||||
class EntityTreeElementExtraEncodeData;
|
||||
|
@ -331,12 +328,6 @@ public:
|
|||
void setEntityInstanceNumber(const quint32&);
|
||||
QString getCertificateID() const;
|
||||
void setCertificateID(const QString& value);
|
||||
QByteArray getStaticCertificateJSON() const;
|
||||
QByteArray getStaticCertificateHash() const;
|
||||
bool verifyStaticCertificateProperties();
|
||||
#ifdef DEBUG_CERT
|
||||
QString computeCertificateID();
|
||||
#endif
|
||||
|
||||
// TODO: get rid of users of getRadius()...
|
||||
float getRadius() const;
|
||||
|
@ -483,6 +474,9 @@ public:
|
|||
ChangeHandlerId registerChangeHandler(const ChangeHandlerCallback& handler);
|
||||
void deregisterChangeHandler(const ChangeHandlerId& changeHandlerId);
|
||||
|
||||
static QString _marketplacePublicKey;
|
||||
static void retrieveMarketplacePublicKey();
|
||||
|
||||
protected:
|
||||
QHash<ChangeHandlerId, ChangeHandlerCallback> _changeHandlers;
|
||||
|
||||
|
|
|
@ -14,6 +14,15 @@
|
|||
#include <QObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
|
@ -1212,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
//
|
||||
// TODO: Implement support for script and visible properties.
|
||||
//
|
||||
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer) {
|
||||
OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) {
|
||||
|
||||
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
|
||||
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
|
||||
|
||||
|
@ -1255,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
QByteArray encodedUpdateDelta = updateDeltaCoder;
|
||||
|
||||
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
||||
EntityPropertyFlags requestedProperties = properties.getChangedProperties();
|
||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||
|
||||
// TODO: we need to handle the multi-pass form of this, similar to how we handle entity data
|
||||
//
|
||||
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
|
||||
// then our modelTreeElementExtraEncodeData should include data about which properties we need to append.
|
||||
//if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) {
|
||||
// requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID());
|
||||
//}
|
||||
|
||||
LevelDetails entityLevel = packetData->startLevel();
|
||||
|
||||
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
|
||||
|
@ -1293,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
int propertyCount = 0;
|
||||
|
||||
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
|
||||
&& successLastUpdatedFits && successPropertyFlagsFits;
|
||||
&& successLastUpdatedFits && successPropertyFlagsFits;
|
||||
|
||||
int startOfEntityItemData = packetData->getUncompressedByteOffset();
|
||||
|
||||
|
@ -1307,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
|
||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
|
||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
|
||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity());
|
||||
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity());
|
||||
|
@ -1463,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
properties.getType() == EntityTypes::Sphere) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
|
||||
}
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
|
||||
|
@ -1513,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
|
||||
// If any part of the model items didn't fit, then the element is considered partial
|
||||
if (appendState != OctreeElement::COMPLETED) {
|
||||
// TODO: handle mechanism for handling partial fitting data!
|
||||
// add this item into our list for the next appendElementData() pass
|
||||
//modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit);
|
||||
|
||||
// for now, if it's not complete, it's not successful
|
||||
success = false;
|
||||
didntFitProperties = propertiesDidntFit;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1534,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
|||
} else {
|
||||
qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer.";
|
||||
success = false;
|
||||
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
|
||||
// maybe we should assert!!!
|
||||
}
|
||||
} else {
|
||||
packetData->discardSubTree();
|
||||
}
|
||||
return success;
|
||||
|
||||
|
||||
return appendState;
|
||||
}
|
||||
|
||||
QByteArray EntityItemProperties::getPackedNormals() const {
|
||||
|
@ -1664,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
|
||||
|
@ -2471,3 +2472,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const {
|
|||
bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
|
||||
return parentRelatedPropertyChanged() || dimensionsChanged();
|
||||
}
|
||||
|
||||
// Checking Certifiable Properties
|
||||
#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N()
|
||||
#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString()
|
||||
#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N())
|
||||
QByteArray EntityItemProperties::getStaticCertificateJSON() const {
|
||||
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
|
||||
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
|
||||
// of the entity as reviewed during the certification submission.
|
||||
|
||||
QJsonObject json;
|
||||
if (!getAnimation().getURL().isEmpty()) {
|
||||
json["animationURL"] = getAnimation().getURL();
|
||||
}
|
||||
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
||||
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
||||
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
||||
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
|
||||
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
|
||||
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
|
||||
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
|
||||
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
|
||||
ADD_STRING_PROPERTY(itemName, ItemName);
|
||||
ADD_INT_PROPERTY(limitedRun, LimitedRun);
|
||||
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
|
||||
ADD_STRING_PROPERTY(modelURL, ModelURL);
|
||||
ADD_STRING_PROPERTY(script, Script);
|
||||
ADD_ENUM_PROPERTY(shapeType, ShapeType);
|
||||
json["type"] = EntityTypes::getEntityTypeName(getType());
|
||||
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
QByteArray EntityItemProperties::getStaticCertificateHash() const {
|
||||
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
|
||||
}
|
||||
|
||||
bool EntityItemProperties::verifyStaticCertificateProperties() {
|
||||
// True IIF a non-empty certificateID matches the static certificate json.
|
||||
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
|
||||
|
||||
if (getCertificateID().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
|
||||
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
|
||||
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
|
||||
|
||||
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
|
||||
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
||||
if (evp_key) {
|
||||
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
|
||||
if (rsa) {
|
||||
const QByteArray digestByteArray = getStaticCertificateHash();
|
||||
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
|
||||
int digestLength = digestByteArray.length();
|
||||
|
||||
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
|
||||
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
|
||||
int signatureLength = signatureByteArray.length();
|
||||
|
||||
ERR_clear_error();
|
||||
bool answer = RSA_verify(NID_sha256,
|
||||
digest,
|
||||
digestLength,
|
||||
signature,
|
||||
signatureLength,
|
||||
rsa);
|
||||
long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
|
||||
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
|
||||
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
|
||||
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
|
||||
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
|
||||
}
|
||||
RSA_free(rsa);
|
||||
if (bio) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
if (evp_key) {
|
||||
EVP_PKEY_free(evp_key);
|
||||
}
|
||||
return answer;
|
||||
} else {
|
||||
if (bio) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
if (evp_key) {
|
||||
EVP_PKEY_free(evp_key);
|
||||
}
|
||||
long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (bio) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,8 +262,8 @@ public:
|
|||
float getLocalRenderAlpha() const { return _localRenderAlpha; }
|
||||
void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
|
||||
|
||||
static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer);
|
||||
static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
|
||||
|
||||
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
|
||||
|
||||
|
@ -336,6 +336,10 @@ public:
|
|||
QByteArray getPackedStrokeColors() const;
|
||||
QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const;
|
||||
|
||||
QByteArray getStaticCertificateJSON() const;
|
||||
QByteArray getStaticCertificateHash() const;
|
||||
bool verifyStaticCertificateProperties();
|
||||
|
||||
protected:
|
||||
QString getCollisionMaskAsString() const;
|
||||
void setCollisionMaskFromString(const QString& maskString);
|
||||
|
|
|
@ -21,8 +21,7 @@ enum EntityPropertyList {
|
|||
// these properties are supported by the EntityItem base class
|
||||
PROP_VISIBLE,
|
||||
PROP_POSITION,
|
||||
PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams
|
||||
PROP_DIMENSIONS = PROP_RADIUS,
|
||||
PROP_DIMENSIONS,
|
||||
PROP_ROTATION,
|
||||
PROP_DENSITY,
|
||||
PROP_VELOCITY,
|
||||
|
@ -47,13 +46,13 @@ enum EntityPropertyList {
|
|||
PROP_ANGULAR_VELOCITY,
|
||||
PROP_ANGULAR_DAMPING,
|
||||
PROP_COLLISIONLESS,
|
||||
PROP_DYNAMIC,
|
||||
PROP_DYNAMIC, // 24
|
||||
|
||||
// property used by Light entity
|
||||
PROP_IS_SPOTLIGHT,
|
||||
PROP_DIFFUSE_COLOR,
|
||||
PROP_AMBIENT_COLOR_UNUSED,
|
||||
PROP_SPECULAR_COLOR_UNUSED,
|
||||
PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||
PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||
PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION
|
||||
PROP_LINEAR_ATTENUATION_UNUSED,
|
||||
PROP_QUADRATIC_ATTENUATION_UNUSED,
|
||||
|
@ -61,30 +60,30 @@ enum EntityPropertyList {
|
|||
PROP_CUTOFF,
|
||||
|
||||
// available to all entities
|
||||
PROP_LOCKED,
|
||||
PROP_LOCKED, // 34
|
||||
|
||||
PROP_TEXTURES, // used by Model entities
|
||||
PROP_ANIMATION_SETTINGS, // used by Model entities
|
||||
PROP_USER_DATA, // all entities
|
||||
PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||
PROP_USER_DATA, // all entities -- 37
|
||||
PROP_SHAPE_TYPE, // used by Model + zones entities
|
||||
|
||||
// used by ParticleEffect entities
|
||||
PROP_MAX_PARTICLES,
|
||||
PROP_LIFESPAN,
|
||||
PROP_MAX_PARTICLES, // 39
|
||||
PROP_LIFESPAN, // 40 -- used by all entities
|
||||
PROP_EMIT_RATE,
|
||||
PROP_EMIT_SPEED,
|
||||
PROP_EMIT_STRENGTH,
|
||||
PROP_EMIT_ACCELERATION,
|
||||
PROP_PARTICLE_RADIUS,
|
||||
PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed????
|
||||
PROP_PARTICLE_RADIUS, // 45!!
|
||||
|
||||
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
|
||||
PROP_MARKETPLACE_ID, // all entities
|
||||
PROP_ACCELERATION, // all entities
|
||||
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
|
||||
PROP_NAME, // all entities
|
||||
PROP_NAME, // all entities -- 50
|
||||
PROP_COLLISION_SOUND_URL,
|
||||
PROP_RESTITUTION,
|
||||
PROP_FRICTION,
|
||||
PROP_FRICTION, // 53
|
||||
|
||||
PROP_VOXEL_VOLUME_SIZE,
|
||||
PROP_VOXEL_DATA,
|
||||
|
@ -96,7 +95,7 @@ enum EntityPropertyList {
|
|||
|
||||
// used by hyperlinks
|
||||
PROP_HREF,
|
||||
PROP_DESCRIPTION,
|
||||
PROP_DESCRIPTION, // 61
|
||||
|
||||
PROP_FACE_CAMERA,
|
||||
PROP_SCRIPT_TIMESTAMP,
|
||||
|
@ -202,6 +201,24 @@ enum EntityPropertyList {
|
|||
PROP_ENTITY_INSTANCE_NUMBER,
|
||||
PROP_CERTIFICATE_ID,
|
||||
|
||||
PROP_HAZE_MODE,
|
||||
|
||||
PROP_HAZE_RANGE,
|
||||
PROP_HAZE_COLOR,
|
||||
PROP_HAZE_GLARE_COLOR,
|
||||
PROP_HAZE_ENABLE_GLARE,
|
||||
PROP_HAZE_GLARE_ANGLE,
|
||||
|
||||
PROP_HAZE_ALTITUDE_EFFECT,
|
||||
PROP_HAZE_CEILING,
|
||||
PROP_HAZE_BASE_REF,
|
||||
|
||||
PROP_HAZE_BACKGROUND_BLEND,
|
||||
|
||||
PROP_HAZE_ATTENUATE_KEYLIGHT,
|
||||
PROP_HAZE_KEYLIGHT_RANGE,
|
||||
PROP_HAZE_KEYLIGHT_ALTITUDE,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
@ -234,24 +251,6 @@ enum EntityPropertyList {
|
|||
PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX,
|
||||
PROP_BACKGROUND_MODE = PROP_MODEL_URL,
|
||||
|
||||
PROP_HAZE_MODE = PROP_COLOR,
|
||||
|
||||
PROP_HAZE_RANGE = PROP_INTENSITY,
|
||||
PROP_HAZE_COLOR = PROP_CUTOFF,
|
||||
PROP_HAZE_GLARE_COLOR = PROP_EXPONENT,
|
||||
PROP_HAZE_ENABLE_GLARE = PROP_IS_SPOTLIGHT,
|
||||
PROP_HAZE_GLARE_ANGLE = PROP_DIFFUSE_COLOR,
|
||||
|
||||
PROP_HAZE_ALTITUDE_EFFECT = PROP_AMBIENT_COLOR_UNUSED,
|
||||
PROP_HAZE_CEILING = PROP_SPECULAR_COLOR_UNUSED,
|
||||
PROP_HAZE_BASE_REF = PROP_LINEAR_ATTENUATION_UNUSED,
|
||||
|
||||
PROP_HAZE_BACKGROUND_BLEND = PROP_QUADRATIC_ATTENUATION_UNUSED,
|
||||
|
||||
PROP_HAZE_ATTENUATE_KEYLIGHT = PROP_ANIMATION_FRAME_INDEX,
|
||||
PROP_HAZE_KEYLIGHT_RANGE = PROP_MODEL_URL,
|
||||
PROP_HAZE_KEYLIGHT_ALTITUDE = PROP_ANIMATION_URL,
|
||||
|
||||
PROP_SKYBOX_COLOR = PROP_ANIMATION_URL,
|
||||
PROP_SKYBOX_URL = PROP_ANIMATION_FPS,
|
||||
PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_PLAYING,
|
||||
|
|
|
@ -1827,24 +1827,9 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
|
|||
_entityTree->withReadLock([&] {
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
||||
if (entity) {
|
||||
result = entity->verifyStaticCertificateProperties();
|
||||
result = entity->getProperties().verifyStaticCertificateProperties();
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_CERT
|
||||
QString EntityScriptingInterface::computeCertificateID(const QUuid& entityID) {
|
||||
QString result { "" };
|
||||
if (_entityTree) {
|
||||
_entityTree->withReadLock([&] {
|
||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
||||
if (entity) {
|
||||
result = entity->computeCertificateID();
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -424,9 +424,6 @@ public slots:
|
|||
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
|
||||
|
||||
Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID);
|
||||
#ifdef DEBUG_CERT
|
||||
Q_INVOKABLE QString computeCertificateID(const QUuid& entityID);
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
|
|
@ -13,6 +13,16 @@
|
|||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QQueue>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include "AccountManager.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <Extents.h>
|
||||
|
@ -62,6 +72,8 @@ EntityTree::EntityTree(bool shouldReaverage) :
|
|||
Octree(shouldReaverage)
|
||||
{
|
||||
resetClientEditStats();
|
||||
|
||||
EntityItem::retrieveMarketplacePublicKey();
|
||||
}
|
||||
|
||||
EntityTree::~EntityTree() {
|
||||
|
@ -228,6 +240,31 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
|
|||
/// Adds a new entity item to the tree
|
||||
void EntityTree::postAddEntity(EntityItemPointer entity) {
|
||||
assert(entity);
|
||||
|
||||
if (getIsServer()) {
|
||||
QString certID(entity->getCertificateID());
|
||||
EntityItemID entityItemID = entity->getEntityItemID();
|
||||
EntityItemID existingEntityItemID;
|
||||
|
||||
{
|
||||
QWriteLocker locker(&_entityCertificateIDMapLock);
|
||||
existingEntityItemID = _entityCertificateIDMap.value(certID);
|
||||
if (!certID.isEmpty()) {
|
||||
_entityCertificateIDMap.insert(certID, entityItemID);
|
||||
qCDebug(entities) << "Certificate ID" << certID << "belongs to" << entityItemID;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an already-existing entity from the tree if it has the same
|
||||
// CertificateID as the entity we're trying to add.
|
||||
if (!existingEntityItemID.isNull()) {
|
||||
qCDebug(entities) << "Certificate ID" << certID << "already exists on entity with ID"
|
||||
<< existingEntityItemID << ". Deleting existing entity.";
|
||||
deleteEntity(existingEntityItemID, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if we need to simulate this entity..
|
||||
if (_simulation) {
|
||||
_simulation->addEntity(entity);
|
||||
|
@ -643,8 +680,16 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
|
|||
theEntity->die();
|
||||
|
||||
if (getIsServer()) {
|
||||
{
|
||||
QWriteLocker entityCertificateIDMapLocker(&_entityCertificateIDMapLock);
|
||||
QString certID = theEntity->getCertificateID();
|
||||
if (theEntity->getEntityItemID() == _entityCertificateIDMap.value(certID)) {
|
||||
_entityCertificateIDMap.remove(certID);
|
||||
}
|
||||
}
|
||||
|
||||
// set up the deleted entities ID
|
||||
QWriteLocker locker(&_recentlyDeletedEntitiesLock);
|
||||
QWriteLocker recentlyDeletedEntitiesLocker(&_recentlyDeletedEntitiesLock);
|
||||
_recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID());
|
||||
} else {
|
||||
// on the client side, we also remember that we deleted this entity, we don't care about the time
|
||||
|
@ -1090,6 +1135,280 @@ bool EntityTree::isScriptInWhitelist(const QString& scriptProperty) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) {
|
||||
QTimer* _challengeOwnershipTimeoutTimer = new QTimer(this);
|
||||
connect(this, &EntityTree::killChallengeOwnershipTimeoutTimer, this, [=](const QString& certID) {
|
||||
QReadLocker locker(&_entityCertificateIDMapLock);
|
||||
EntityItemID id = _entityCertificateIDMap.value(certID);
|
||||
if (entityItemID == id && _challengeOwnershipTimeoutTimer) {
|
||||
_challengeOwnershipTimeoutTimer->stop();
|
||||
_challengeOwnershipTimeoutTimer->deleteLater();
|
||||
}
|
||||
});
|
||||
connect(_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||
qCDebug(entities) << "Ownership challenge timed out, deleting entity" << entityItemID;
|
||||
deleteEntity(entityItemID, true);
|
||||
if (_challengeOwnershipTimeoutTimer) {
|
||||
_challengeOwnershipTimeoutTimer->stop();
|
||||
_challengeOwnershipTimeoutTimer->deleteLater();
|
||||
}
|
||||
});
|
||||
_challengeOwnershipTimeoutTimer->setSingleShot(true);
|
||||
_challengeOwnershipTimeoutTimer->start(5000);
|
||||
}
|
||||
|
||||
void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
|
||||
qCDebug(entities) << "'transfer_status' is 'pending', checking again in 90 seconds..." << entityItemID;
|
||||
QTimer* transferStatusRetryTimer = new QTimer(this);
|
||||
connect(transferStatusRetryTimer, &QTimer::timeout, this, [=]() {
|
||||
validatePop(certID, entityItemID, senderNode, true);
|
||||
});
|
||||
transferStatusRetryTimer->setSingleShot(true);
|
||||
transferStatusRetryTimer->start(90000);
|
||||
}
|
||||
|
||||
QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QString ownerKey) {
|
||||
QString ownerKeyWithHeaders = ("-----BEGIN RSA PUBLIC KEY-----\n" + ownerKey + "\n-----END RSA PUBLIC KEY-----");
|
||||
BIO* bio = BIO_new_mem_buf((void*)ownerKeyWithHeaders.toUtf8().constData(), -1);
|
||||
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // NO NEWLINE
|
||||
RSA* rsa = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
|
||||
|
||||
if (rsa) {
|
||||
QUuid nonce = QUuid::createUuid();
|
||||
const unsigned int textLength = nonce.toString().length();
|
||||
QByteArray encryptedText(RSA_size(rsa), 0);
|
||||
const int encryptStatus = RSA_public_encrypt(textLength,
|
||||
reinterpret_cast<const unsigned char*>(qPrintable(nonce.toString())),
|
||||
reinterpret_cast<unsigned char*>(encryptedText.data()),
|
||||
rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
if (bio) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
RSA_free(rsa);
|
||||
if (encryptStatus == -1) {
|
||||
long error = ERR_get_error();
|
||||
const char* error_str = ERR_error_string(error, NULL);
|
||||
qCWarning(entities) << "Unable to compute encrypted nonce for" << certID << "\nRSA error:" << error_str;
|
||||
return "";
|
||||
}
|
||||
|
||||
QWriteLocker locker(&_certNonceMapLock);
|
||||
_certNonceMap.insert(certID, nonce);
|
||||
|
||||
return encryptedText;
|
||||
} else {
|
||||
if (bio) {
|
||||
BIO_free(bio);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) {
|
||||
{
|
||||
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
|
||||
id = _entityCertificateIDMap.value(certID);
|
||||
}
|
||||
|
||||
QString actualNonce;
|
||||
{
|
||||
QWriteLocker locker(&_certNonceMapLock);
|
||||
actualNonce = _certNonceMap.take(certID).toString();
|
||||
}
|
||||
|
||||
bool verificationSuccess = (actualNonce == decryptedNonce);
|
||||
|
||||
if (verificationSuccess) {
|
||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
|
||||
} else {
|
||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed."
|
||||
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
|
||||
}
|
||||
|
||||
return verificationSuccess;
|
||||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
int certIDByteArraySize;
|
||||
int encryptedTextByteArraySize;
|
||||
int nodeToChallengeByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&encryptedTextByteArraySize);
|
||||
message.readPrimitive(&nodeToChallengeByteArraySize);
|
||||
|
||||
QByteArray certID(message.read(certIDByteArraySize));
|
||||
QByteArray encryptedText(message.read(encryptedTextByteArraySize));
|
||||
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
|
||||
|
||||
sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode);
|
||||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
int certIDByteArraySize;
|
||||
int decryptedTextByteArraySize;
|
||||
int challengingNodeUUIDByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&decryptedTextByteArraySize);
|
||||
message.readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||
|
||||
QByteArray certID(message.read(certIDByteArraySize));
|
||||
QByteArray decryptedText(message.read(decryptedTextByteArraySize));
|
||||
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
|
||||
|
||||
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||
certIDByteArraySize + decryptedText.length() + 2 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipReplyPacket->writePrimitive(decryptedText.length());
|
||||
challengeOwnershipReplyPacket->write(certID);
|
||||
challengeOwnershipReplyPacket->write(decryptedText);
|
||||
|
||||
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
|
||||
}
|
||||
|
||||
void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
|
||||
// 1. Encrypt a nonce with the owner's public key
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey);
|
||||
|
||||
if (encryptedText == "") {
|
||||
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
|
||||
deleteEntity(entityItemID, true);
|
||||
} else {
|
||||
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
|
||||
// 2. Send the encrypted text to the rezzing avatar's node
|
||||
QByteArray certIDByteArray = certID.toUtf8();
|
||||
int certIDByteArraySize = certIDByteArray.size();
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(encryptedText.length());
|
||||
challengeOwnershipPacket->write(certIDByteArray);
|
||||
challengeOwnershipPacket->write(encryptedText);
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
|
||||
|
||||
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID));
|
||||
return;
|
||||
} else {
|
||||
startChallengeOwnershipTimer(entityItemID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
|
||||
// to make sure belongs to Avatar B.
|
||||
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
|
||||
|
||||
int certIDByteArraySize = certID.length();
|
||||
int encryptedTextByteArraySize = encryptedText.length();
|
||||
int senderNodeUUIDSize = senderNodeUUID.length();
|
||||
|
||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||
certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
|
||||
true);
|
||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
|
||||
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
|
||||
challengeOwnershipPacket->write(certID);
|
||||
challengeOwnershipPacket->write(encryptedText);
|
||||
challengeOwnershipPacket->write(senderNodeUUID);
|
||||
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge))));
|
||||
}
|
||||
|
||||
void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) {
|
||||
// Start owner verification.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
// First, asynchronously hit "proof_of_purchase_status?transaction_type=transfer" endpoint.
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = certID;
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||
jsonObject = jsonObject["data"].toObject();
|
||||
|
||||
if (networkReply->error() == QNetworkReply::NoError) {
|
||||
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
|
||||
qCDebug(entities) << "invalid_reason not empty, deleting entity" << entityItemID;
|
||||
deleteEntity(entityItemID, true);
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
|
||||
qCDebug(entities) << "'transfer_status' is 'failed', deleting entity" << entityItemID;
|
||||
deleteEntity(entityItemID, true);
|
||||
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
|
||||
if (isRetryingValidation) {
|
||||
qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID;
|
||||
deleteEntity(entityItemID, true);
|
||||
} else {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer",
|
||||
Q_ARG(const QString&, certID),
|
||||
Q_ARG(const EntityItemID&, entityItemID),
|
||||
Q_ARG(const SharedNodePointer&, senderNode));
|
||||
return;
|
||||
} else {
|
||||
startPendingTransferStatusTimer(certID, entityItemID, senderNode);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Second, challenge ownership of the PoP cert
|
||||
sendChallengeOwnershipPacket(certID,
|
||||
jsonObject["transfer_recipient_key"].toString(),
|
||||
entityItemID,
|
||||
senderNode);
|
||||
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID
|
||||
<< "More info:" << jsonObject;
|
||||
deleteEntity(entityItemID, true);
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
int certIDByteArraySize;
|
||||
int decryptedTextByteArraySize;
|
||||
|
||||
message.readPrimitive(&certIDByteArraySize);
|
||||
message.readPrimitive(&decryptedTextByteArraySize);
|
||||
|
||||
QString certID(message.read(certIDByteArraySize));
|
||||
QString decryptedText(message.read(decryptedTextByteArraySize));
|
||||
|
||||
emit killChallengeOwnershipTimeoutTimer(certID);
|
||||
|
||||
EntityItemID id;
|
||||
if (!verifyDecryptedNonce(certID, decryptedText, id)) {
|
||||
if (!id.isNull()) {
|
||||
deleteEntity(id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||
const SharedNodePointer& senderNode) {
|
||||
|
||||
|
@ -1265,13 +1584,17 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
_totalUpdates++;
|
||||
} else if (isAdd) {
|
||||
bool failedAdd = !allowed;
|
||||
bool isCertified = !properties.getCertificateID().isEmpty();
|
||||
if (!allowed) {
|
||||
qCDebug(entities) << "Filtered entity add. ID:" << entityItemID;
|
||||
} else if (!senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
||||
} else if (!isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User without 'rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add an entity ID:" << entityItemID;
|
||||
|
||||
qCDebug(entities) << "User without 'uncertified rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add an uncertified entity with ID:" << entityItemID;
|
||||
} else if (isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
|
||||
failedAdd = true;
|
||||
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
|
||||
<< "] attempted to add a certified entity with ID:" << entityItemID;
|
||||
} else {
|
||||
// this is a new entity... assign a new entityID
|
||||
properties.setCreated(properties.getLastEdited());
|
||||
|
@ -1280,6 +1603,19 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
EntityItemPointer newEntity = addEntity(entityItemID, properties);
|
||||
endCreate = usecTimestampNow();
|
||||
_totalCreates++;
|
||||
|
||||
if (newEntity && isCertified && getIsServer()) {
|
||||
if (!properties.verifyStaticCertificateProperties()) {
|
||||
qCDebug(entities) << "User" << senderNode->getUUID()
|
||||
<< "attempted to add a certified entity with ID" << entityItemID << "which failed"
|
||||
<< "static certificate verification.";
|
||||
// Delete the entity we just added if it doesn't pass static certificate verification
|
||||
deleteEntity(entityItemID, true);
|
||||
} else {
|
||||
validatePop(properties.getCertificateID(), entityItemID, senderNode, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (newEntity) {
|
||||
newEntity->markAsChangedOnServer();
|
||||
notifyNewlyCreatedEntity(*newEntity, senderNode);
|
||||
|
|
|
@ -93,6 +93,9 @@ public:
|
|||
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties);
|
||||
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||
const SharedNodePointer& senderNode) override;
|
||||
virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
|
||||
|
@ -181,6 +184,11 @@ public:
|
|||
return _recentlyDeletedEntityItemIDs;
|
||||
}
|
||||
|
||||
QHash<QString, EntityItemID> getEntityCertificateIDMap() const {
|
||||
QReadLocker locker(&_entityCertificateIDMapLock);
|
||||
return _entityCertificateIDMap;
|
||||
}
|
||||
|
||||
void forgetEntitiesDeletedBefore(quint64 sinceTime);
|
||||
|
||||
int processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode);
|
||||
|
@ -267,6 +275,9 @@ public:
|
|||
|
||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||
|
||||
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey);
|
||||
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id);
|
||||
|
||||
signals:
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void deletingEntityPointer(EntityItem* entityID);
|
||||
|
@ -276,6 +287,7 @@ signals:
|
|||
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);
|
||||
void newCollisionSoundURL(const QUrl& url, const EntityItemID& entityID);
|
||||
void clearingEntities();
|
||||
void killChallengeOwnershipTimeoutTimer(const QString& certID);
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -316,6 +328,12 @@ protected:
|
|||
mutable QReadWriteLock _entityMapLock;
|
||||
QHash<EntityItemID, EntityItemPointer> _entityMap;
|
||||
|
||||
mutable QReadWriteLock _entityCertificateIDMapLock;
|
||||
QHash<QString, EntityItemID> _entityCertificateIDMap;
|
||||
|
||||
mutable QReadWriteLock _certNonceMapLock;
|
||||
QHash<QString, QUuid> _certNonceMap;
|
||||
|
||||
EntitySimulationPointer _simulation;
|
||||
|
||||
bool _wantEditLogging = false;
|
||||
|
@ -357,6 +375,14 @@ protected:
|
|||
|
||||
MovingEntitiesOperator _entityMover;
|
||||
QHash<EntityItemID, EntityItemPointer> _entitiesToAdd;
|
||||
|
||||
Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID);
|
||||
Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||
|
||||
private:
|
||||
void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
|
||||
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -15,19 +15,6 @@
|
|||
#include "EntityItemProperties.h"
|
||||
#include "EntityItemPropertiesMacros.h"
|
||||
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_RANGE{ 1000.0f };
|
||||
const xColor HazePropertyGroup::DEFAULT_HAZE_COLOR{ 128, 154, 179 }; // Bluish
|
||||
const xColor HazePropertyGroup::DEFAULT_HAZE_GLARE_COLOR{ 255, 229, 179 }; // Yellowish
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_GLARE_ANGLE{ 20.0 };
|
||||
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_CEILING{ 200.0f };
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_BASE_REF{ 0.0f };
|
||||
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_BACKGROUND_BLEND{ 0.0f };
|
||||
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_RANGE{ 1000.0 };
|
||||
const float HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_ALTITUDE{ 200.0f };
|
||||
|
||||
void HazePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const {
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_RANGE, Haze, haze, HazeRange, hazeRange);
|
||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_COLOR, Haze, haze, HazeColor, hazeColor);
|
||||
|
@ -306,6 +293,7 @@ EntityPropertyFlags HazePropertyGroup::getEntityProperties(EncodeBitstreamParams
|
|||
requestedProperties += PROP_HAZE_ENABLE_GLARE;
|
||||
requestedProperties += PROP_HAZE_GLARE_ANGLE;
|
||||
|
||||
requestedProperties += PROP_HAZE_ALTITUDE_EFFECT;
|
||||
requestedProperties += PROP_HAZE_CEILING;
|
||||
requestedProperties += PROP_HAZE_BASE_REF;
|
||||
|
||||
|
|
|
@ -27,6 +27,19 @@ class OctreePacketData;
|
|||
class EntityTreeElementExtraEncodeData;
|
||||
class ReadBitstreamToTreeParams;
|
||||
|
||||
static const float INITIAL_HAZE_RANGE{ 1000.0f };
|
||||
static const xColor initialHazeGlareColorXcolor{ 255, 229, 179 };
|
||||
static const xColor initialHazeColorXcolor{ 128, 154, 179 };
|
||||
static const float INITIAL_HAZE_GLARE_ANGLE{ 20.0f };
|
||||
|
||||
static const float INITIAL_HAZE_BASE_REFERENCE{ 0.0f };
|
||||
static const float INITIAL_HAZE_HEIGHT{ 200.0f };
|
||||
|
||||
static const float INITIAL_HAZE_BACKGROUND_BLEND{ 0.0f };
|
||||
|
||||
static const float INITIAL_KEY_LIGHT_RANGE{ 1000.0f };
|
||||
static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f };
|
||||
|
||||
class HazePropertyGroup : public PropertyGroup {
|
||||
public:
|
||||
// EntityItemProperty related helpers
|
||||
|
@ -74,38 +87,25 @@ public:
|
|||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) override;
|
||||
|
||||
static const float DEFAULT_HAZE_RANGE;
|
||||
static const xColor DEFAULT_HAZE_COLOR;
|
||||
static const xColor DEFAULT_HAZE_GLARE_COLOR;
|
||||
static const float DEFAULT_HAZE_GLARE_ANGLE;
|
||||
|
||||
static const float DEFAULT_HAZE_CEILING;
|
||||
static const float DEFAULT_HAZE_BASE_REF;
|
||||
|
||||
static const float DEFAULT_HAZE_BACKGROUND_BLEND;
|
||||
|
||||
static const float DEFAULT_HAZE_KEYLIGHT_RANGE;
|
||||
static const float DEFAULT_HAZE_KEYLIGHT_ALTITUDE;
|
||||
|
||||
// Range only parameters
|
||||
DEFINE_PROPERTY(PROP_HAZE_RANGE, HazeRange, hazeRange, float, DEFAULT_HAZE_RANGE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_COLOR, HazeColor, hazeColor, xColor, DEFAULT_HAZE_COLOR);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_COLOR, HazeGlareColor, hazeGlareColor, xColor, DEFAULT_HAZE_GLARE_COLOR);
|
||||
DEFINE_PROPERTY(PROP_HAZE_RANGE, HazeRange, hazeRange, float, INITIAL_HAZE_RANGE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_COLOR, HazeColor, hazeColor, xColor, initialHazeColorXcolor);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_COLOR, HazeGlareColor, hazeGlareColor, xColor, initialHazeGlareColorXcolor);
|
||||
DEFINE_PROPERTY(PROP_HAZE_ENABLE_GLARE, HazeEnableGlare, hazeEnableGlare, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_ANGLE, HazeGlareAngle, hazeGlareAngle, float, DEFAULT_HAZE_GLARE_ANGLE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_ANGLE, HazeGlareAngle, hazeGlareAngle, float, INITIAL_HAZE_GLARE_ANGLE);
|
||||
|
||||
// Altitude parameters
|
||||
DEFINE_PROPERTY(PROP_HAZE_ALTITUDE_EFFECT, HazeAltitudeEffect, hazeAltitudeEffect, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_CEILING, HazeCeiling, hazeCeiling, float, DEFAULT_HAZE_CEILING);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_BASE_REF, HazeBaseRef, hazeBaseRef, float, DEFAULT_HAZE_BASE_REF);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_CEILING, HazeCeiling, hazeCeiling, float, INITIAL_HAZE_BASE_REFERENCE + INITIAL_HAZE_HEIGHT);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_BASE_REF, HazeBaseRef, hazeBaseRef, float, INITIAL_HAZE_BASE_REFERENCE);
|
||||
|
||||
// Background (skybox) blend value
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_BACKGROUND_BLEND, HazeBackgroundBlend, hazeBackgroundBlend, float, DEFAULT_HAZE_BACKGROUND_BLEND);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_BACKGROUND_BLEND, HazeBackgroundBlend, hazeBackgroundBlend, float, INITIAL_HAZE_BACKGROUND_BLEND);
|
||||
|
||||
// hazeDirectional light attenuation
|
||||
DEFINE_PROPERTY(PROP_HAZE_ATTENUATE_KEYLIGHT, HazeAttenuateKeyLight, hazeAttenuateKeyLight, bool, false);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_RANGE, HazeKeyLightRange, hazeKeyLightRange, float, DEFAULT_HAZE_KEYLIGHT_RANGE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_ALTITUDE, HazeKeyLightAltitude, hazeKeyLightAltitude, float, DEFAULT_HAZE_KEYLIGHT_ALTITUDE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_RANGE, HazeKeyLightRange, hazeKeyLightRange, float, INITIAL_KEY_LIGHT_RANGE);
|
||||
DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_ALTITUDE, HazeKeyLightAltitude, hazeKeyLightAltitude, float, INITIAL_KEY_LIGHT_ALTITUDE);
|
||||
};
|
||||
|
||||
#endif // hifi_HazePropertyGroup_h
|
||||
|
|
|
@ -51,12 +51,12 @@ namespace entity {
|
|||
}
|
||||
}
|
||||
|
||||
// shapeCalculator is a hook for external code that knows how to configure a ShapeInfo
|
||||
// hullShapeCalculator is a hook for external code that knows how to configure a ShapeInfo
|
||||
// for given entity::Shape and dimensions
|
||||
ShapeEntityItem::ShapeInfoCalculator shapeCalculator = nullptr;
|
||||
ShapeEntityItem::ShapeInfoCalculator hullShapeCalculator = nullptr;
|
||||
|
||||
void ShapeEntityItem::setShapeInfoCalulator(ShapeEntityItem::ShapeInfoCalculator callback) {
|
||||
shapeCalculator = callback;
|
||||
hullShapeCalculator = callback;
|
||||
}
|
||||
|
||||
ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
|
@ -104,6 +104,14 @@ void ShapeEntityItem::setShape(const entity::Shape& shape) {
|
|||
case entity::Shape::Sphere:
|
||||
_type = EntityTypes::Sphere;
|
||||
break;
|
||||
case entity::Shape::Circle:
|
||||
// Circle is implicitly flat so we enforce flat dimensions
|
||||
setDimensions(getDimensions());
|
||||
break;
|
||||
case entity::Shape::Quad:
|
||||
// Quad is implicitly flat so we enforce flat dimensions
|
||||
setDimensions(getDimensions());
|
||||
break;
|
||||
default:
|
||||
_type = EntityTypes::Shape;
|
||||
break;
|
||||
|
@ -196,6 +204,18 @@ void ShapeEntityItem::setColor(const QColor& value) {
|
|||
setAlpha(value.alpha());
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setDimensions(const glm::vec3& value) {
|
||||
const float MAX_FLAT_DIMENSION = 0.0001f;
|
||||
if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) {
|
||||
// enforce flatness in Y
|
||||
glm::vec3 newDimensions = value;
|
||||
newDimensions.y = MAX_FLAT_DIMENSION;
|
||||
EntityItem::setDimensions(newDimensions);
|
||||
} else {
|
||||
EntityItem::setDimensions(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShapeEntityItem::supportsDetailedRayIntersection() const {
|
||||
return _shape == entity::Sphere;
|
||||
}
|
||||
|
@ -249,19 +269,13 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
const glm::vec3 entityDimensions = getDimensions();
|
||||
|
||||
switch (_shape){
|
||||
case entity::Shape::Quad: {
|
||||
// Not in GeometryCache::buildShapes, unsupported.
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
//TODO WL21389: Add a SHAPE_TYPE_QUAD ShapeType and treat
|
||||
// as a special box (later if desired support)
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Quad:
|
||||
// Quads collide like flat Cubes
|
||||
case entity::Shape::Cube: {
|
||||
_collisionShapeType = SHAPE_TYPE_BOX;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Sphere: {
|
||||
|
||||
float diameter = entityDimensions.x;
|
||||
const float MIN_DIAMETER = 0.001f;
|
||||
const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f;
|
||||
|
@ -275,25 +289,28 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Circle: {
|
||||
_collisionShapeType = SHAPE_TYPE_CIRCLE;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Circle:
|
||||
// Circles collide like flat Cylinders
|
||||
case entity::Shape::Cylinder: {
|
||||
_collisionShapeType = SHAPE_TYPE_CYLINDER_Y;
|
||||
// TODO WL21389: determine if rotation is axis-aligned
|
||||
//const Transform::Quat & rot = _transform.getRotation();
|
||||
|
||||
// TODO WL21389: some way to tell apart SHAPE_TYPE_CYLINDER_Y, _X, _Z based on rotation and
|
||||
// hull ( or dimensions, need circular cross section)
|
||||
// Should allow for minor variance along axes?
|
||||
|
||||
float diameter = entityDimensions.x;
|
||||
const float MIN_DIAMETER = 0.001f;
|
||||
const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f;
|
||||
if (diameter > MIN_DIAMETER
|
||||
&& fabsf(diameter - entityDimensions.z) / diameter < MIN_RELATIVE_SPHERICAL_ERROR) {
|
||||
_collisionShapeType = SHAPE_TYPE_SPHERE;
|
||||
} else if (hullShapeCalculator) {
|
||||
hullShapeCalculator(this, info);
|
||||
_collisionShapeType = SHAPE_TYPE_SIMPLE_HULL;
|
||||
} else {
|
||||
// woops, someone forgot to hook up the hullShapeCalculator()!
|
||||
// final fallback is ellipsoid
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Cone: {
|
||||
if (shapeCalculator) {
|
||||
shapeCalculator(this, info);
|
||||
// shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL)
|
||||
if (hullShapeCalculator) {
|
||||
hullShapeCalculator(this, info);
|
||||
_collisionShapeType = SHAPE_TYPE_SIMPLE_HULL;
|
||||
} else {
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
|
@ -304,9 +321,8 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
case entity::Shape::Triangle:
|
||||
case entity::Shape::Hexagon:
|
||||
case entity::Shape::Octagon: {
|
||||
if (shapeCalculator) {
|
||||
shapeCalculator(this, info);
|
||||
// shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL)
|
||||
if (hullShapeCalculator) {
|
||||
hullShapeCalculator(this, info);
|
||||
_collisionShapeType = SHAPE_TYPE_SIMPLE_HULL;
|
||||
} else {
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
|
@ -318,9 +334,8 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
case entity::Shape::Octahedron:
|
||||
case entity::Shape::Dodecahedron:
|
||||
case entity::Shape::Icosahedron: {
|
||||
if ( shapeCalculator ) {
|
||||
shapeCalculator(this, info);
|
||||
// shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL)
|
||||
if ( hullShapeCalculator ) {
|
||||
hullShapeCalculator(this, info);
|
||||
_collisionShapeType = SHAPE_TYPE_SIMPLE_HULL;
|
||||
} else {
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
|
@ -330,7 +345,7 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
case entity::Shape::Torus: {
|
||||
// Not in GeometryCache::buildShapes, unsupported.
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
//TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later if desired support)
|
||||
//TODO handle this shape more correctly
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue