From acdd1e32e4080ea1844a07b616e22f4ecb867e22 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 12:11:07 -0700 Subject: [PATCH 1/5] First round --- .../qml/hifi/commerce/checkout/Checkout.qml | 4 +++- .../qml/hifi/commerce/purchases/Purchases.qml | 12 ++++++------ .../resources/qml/hifi/commerce/wallet/Wallet.qml | 4 +++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 09c2f6fa76..38a938c535 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -79,6 +79,8 @@ Rectangle { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; } else { + root.itemHref = result.data.download_url; + console.log("ZRF TEST " + root.itemHref); root.activeView = "checkoutSuccess"; } } @@ -125,7 +127,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 990fd348c6..ea32c139d4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -78,6 +78,10 @@ Rectangle { onInventoryResult: { purchasesReceived = true; + if (root.pendingInventoryReply) { + inventoryTimer.start(); + } + if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { @@ -98,10 +102,6 @@ Rectangle { previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); - - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } } root.pendingInventoryReply = false; @@ -112,7 +112,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'purchases_walletNotSetUp'}); } } @@ -426,7 +426,7 @@ Rectangle { itemName: title; itemId: id; itemPreviewImageUrl: preview; - itemHref: root_file_url; + itemHref: download_url; purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 9056d5bed3..9beadd3361 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -176,6 +176,8 @@ Rectangle { commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); + } else { + sendToScript({method: 'goToMarketplaceItemPage', itemId: msg.referrer}); } } else if (msg.method === 'walletSetup_raiseKeyboard') { root.keyboardRaised = true; @@ -283,7 +285,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - root.activeView = "walletHome"; + commerce.getWalletStatus(); } else { sendToScript(msg); } From 733df8391f367b6d779aa0f65d55b4d6f21c1ae3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 12:17:26 -0700 Subject: [PATCH 2/5] Round 2 --- .../qml/hifi/commerce/checkout/Checkout.qml | 1 - interface/src/commerce/QmlCommerce.cpp | 32 ++-------------- interface/src/commerce/QmlCommerce.h | 7 ---- interface/src/commerce/Wallet.cpp | 38 +++++++++++++++++++ interface/src/commerce/Wallet.h | 13 +++++++ .../scripting/WalletScriptingInterface.cpp | 5 +++ .../src/scripting/WalletScriptingInterface.h | 3 ++ .../ui/overlays/ContextOverlayInterface.cpp | 8 +++- scripts/system/commerce/wallet.js | 5 +++ scripts/system/html/js/marketplacesInject.js | 1 - scripts/system/marketplaces/marketplaces.js | 19 ++++++++-- scripts/system/notifications.js | 5 ++- 12 files changed, 93 insertions(+), 44 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 38a938c535..e2ecf927a5 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -80,7 +80,6 @@ Rectangle { root.activeView = "checkoutFailure"; } else { root.itemHref = result.data.download_url; - console.log("ZRF TEST " + root.itemHref); root.activeView = "checkoutSuccess"; } } diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 9f8847e8c7..ee75bc59e3 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -15,7 +15,6 @@ #include "Ledger.h" #include "Wallet.h" #include -#include "scripting/WalletScriptingInterface.h" HIFI_QML_DEF(QmlCommerce) @@ -29,37 +28,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult); connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); - connect(ledger.data(), &Ledger::accountResult, this, [&]() { - auto wallet = DependencyManager::get(); - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; - } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; - } else { - status = (uint)WalletStatus::WALLET_STATUS_READY; - } - - walletScriptingInterface->setWalletStatus(status); - emit walletStatusResult(status); - }); + connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); } void QmlCommerce::getWalletStatus() { - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (DependencyManager::get()->isLoggedIn()) { - // This will set account info for the wallet, allowing us to decrypt and display the security image. - account(); - } else { - status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; - emit walletStatusResult(status); - walletScriptingInterface->setWalletStatus(status); - return; - } + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); } void QmlCommerce::getLoginStatus() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 8e6af6da65..45a5360680 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -27,13 +27,6 @@ class QmlCommerce : public OffscreenQmlDialog { public: QmlCommerce(QQuickItem* parent = nullptr); - enum WalletStatus { - WALLET_STATUS_NOT_LOGGED_IN = 0, - WALLET_STATUS_NOT_SET_UP, - WALLET_STATUS_NOT_AUTHENTICATED, - WALLET_STATUS_READY - }; - signals: void walletStatusResult(uint walletStatus); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 079e3a9479..fc16b85439 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -282,9 +282,27 @@ void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArra Wallet::Wallet() { auto nodeList = DependencyManager::get(); + auto ledger = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + + connect(ledger.data(), &Ledger::accountResult, this, [&]() { + auto wallet = DependencyManager::get(); + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; + } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; + } else { + status = (uint)WalletStatus::WALLET_STATUS_READY; + } + + walletScriptingInterface->setWalletStatus(status); + emit walletStatusResult(status); + }); } Wallet::~Wallet() { @@ -682,3 +700,23 @@ bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString decryptedText = QString("hello"); return true; } + +void Wallet::account() { + auto ledger = DependencyManager::get(); + ledger->account(); +} + +void Wallet::getWalletStatus() { + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (DependencyManager::get()->isLoggedIn()) { + // This will set account info for the wallet, allowing us to decrypt and display the security image. + account(); + } else { + status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; + emit walletStatusResult(status); + walletScriptingInterface->setWalletStatus(status); + return; + } +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index acf9f8e45e..38c5299810 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -17,6 +17,7 @@ #include #include #include +#include "scripting/WalletScriptingInterface.h" #include @@ -50,10 +51,20 @@ public: void reset(); + void getWalletStatus(); + enum WalletStatus { + WALLET_STATUS_NOT_LOGGED_IN = 0, + WALLET_STATUS_NOT_SET_UP, + WALLET_STATUS_NOT_AUTHENTICATED, + WALLET_STATUS_READY + }; + signals: void securityImageResult(bool exists); void keyFilePathIfExistsResult(const QString& path); + void walletStatusResult(uint walletStatus); + private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); @@ -71,6 +82,8 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText); + + void account(); }; #endif // hifi_Wallet_h diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 555e9477b0..99fdd5fbde 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -18,6 +18,11 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q WalletScriptingInterface::WalletScriptingInterface() { } +void WalletScriptingInterface::refreshWalletStatus() { + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); +} + static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { if (QThread::currentThread() != thread()) { diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 31b42094cf..038c580197 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -20,6 +20,7 @@ #include #include #include "Application.h" +#include "commerce/Wallet.h" class CheckoutProxy : public QmlWrapper { Q_OBJECT @@ -36,6 +37,7 @@ class WalletScriptingInterface : public QObject, public Dependency { public: WalletScriptingInterface(); + Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } void setWalletStatus(const uint& status) { _walletStatus = status; } @@ -43,6 +45,7 @@ public: signals: void walletStatusChanged(); + void walletNotSetup(); private: uint _walletStatus; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 39fd4f9377..8cbb214857 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -41,6 +41,7 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_MARKETPLACE_ID; _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; + _entityPropertyFlags += PROP_CERTIFICATE_ID; auto entityTreeRenderer = DependencyManager::get().data(); connect(entityTreeRenderer, SIGNAL(mousePressOnEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(createOrDestroyContextOverlay(const EntityItemID&, const PointerEvent&))); @@ -176,7 +177,12 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); - return (entityProperties.getMarketplaceID().length() != 0); + Setting::Handle _settingSwitch{ "commerce", false }; + if (_settingSwitch.get()) { + return (entityProperties.getCertificateID().length() != 0); + } else { + return (entityProperties.getMarketplaceID().length() != 0); + } } bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 7553ca4eeb..04b67ec14f 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -16,6 +16,8 @@ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); + var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; + // Function Name: onButtonClicked() // // Description: @@ -88,6 +90,9 @@ case 'goToPurchases': tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; + case 'goToMarketplaceItemPage': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index ded4542c51..41db724c3f 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -334,7 +334,6 @@ $('body').addClass("code-injected"); maybeAddLogInButton(); - maybeAddSetupWalletButton(); changeDropdownMenu(); var purchaseButton = $('#side-info').find('.btn').first(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e94b227a4a..deeae0d299 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -129,6 +129,10 @@ } } + function openWallet() { + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + } + function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) { wireEventBridge(true); tablet.sendToQml({ @@ -158,6 +162,7 @@ Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.refreshWalletStatus(); function onMessage(message) { @@ -214,7 +219,7 @@ } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); } else if (parsedJsonMessage.type === "MY_ITEMS") { referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; @@ -281,16 +286,22 @@ case 'purchases_openWallet': case 'checkout_openWallet': case 'checkout_setUpClicked': - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); break; case 'purchases_walletNotSetUp': - case 'checkout_walletNotSetUp': wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', referrer: "purchases" }); - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); + case 'checkout_walletNotSetUp': + wireEventBridge(true); + tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: message.itemId + }); + openWallet(); break; case 'checkout_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index ce693b6339..dc0fb1daf9 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -95,13 +95,15 @@ EDIT_ERROR: 4, TABLET: 5, CONNECTION: 6, + WALLET: 7, properties: [ { text: "Snapshot" }, { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" }, { text: "Tablet" }, - { text: "Connection" } + { text: "Connection" }, + { text: "Wallet" } ], getTypeFromMenuItem: function (menuItemName) { var type; @@ -691,6 +693,7 @@ Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); + Wallet.walletNotSetup.connect(walletNotSetup); Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); Messages.messageReceived.connect(onMessageReceived); From 38b95b54031ae9e4bd2a90ce6b4d9d330f1bd520 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 15:07:14 -0700 Subject: [PATCH 3/5] Instructions HTML file and flow --- .../html/commerce/backup_instructions.html | 609 ++++++++++++++++++ .../qml/hifi/commerce/wallet/Help.qml | 8 +- .../qml/hifi/commerce/wallet/Security.qml | 15 +- .../qml/hifi/commerce/wallet/WalletSetup.qml | 19 +- interface/src/commerce/Wallet.cpp | 37 +- 5 files changed, 672 insertions(+), 16 deletions(-) create mode 100644 interface/resources/html/commerce/backup_instructions.html diff --git a/interface/resources/html/commerce/backup_instructions.html b/interface/resources/html/commerce/backup_instructions.html new file mode 100644 index 0000000000..560894e33d --- /dev/null +++ b/interface/resources/html/commerce/backup_instructions.html @@ -0,0 +1,609 @@ + + + + + +Backing Up Your Private Keys | High Fidelity + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +

Backing Up Your Private Keys

+
+ + +

What are private keys?

+

A private key is a secret piece of text that is used to prove ownership, unlock confidential information and sign transactions.

+

In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.

+ +
+

Where are my private keys stored?"

+

By default, your private keys are only stored on your hard drive in High Fidelity Interface's AppData directory.

+

Here is the file path of your hifikey - you can browse to it using your file explorer.

+
HIFIKEY_PATH_REPLACEME
+ +
+

How should I make a backup of my private keys?

+

You should backup your .hifikey file above by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. Restore your backup by replacing the file in Interface's AppData directory with your backed-up copy.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

What happens if I lose my passphrase?

+

Your passphrase is used to encrypt your private keys. If you lose your passphrase, you will no longer be able to decrypt your private key file nor have access to the contents of your Wallet or My Purchases.

+

In order to guarantee your privacy, nobody can help you recover your passphrase, including High Fidelity. + +

Please write it down and store it securely.

+

 

+
+ +

Want to learn more?

+

You can find out much more about the blockchain and about commerce in High Fidelity by visiting our Docs site:

+

Visit High Fidelity's Docs

+
+ +
+
+ +
+ + diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 21548ea788..fb0fd7a76e 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -30,12 +30,14 @@ Item { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } - Component.onCompleted: { - commerce.getKeyFilePathIfExists(); + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } } RalewaySemiBold { diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 9b70bb1f71..0f2edbe913 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -25,13 +25,13 @@ Item { HifiConstants { id: hifi; } id: root; - property string keyFilePath: ""; + property string keyFilePath; Hifi.QmlCommerce { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } @@ -232,6 +232,12 @@ Item { anchors.rightMargin: 55; anchors.bottom: parent.bottom; + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } + } + HiFiGlyphs { id: yourPrivateKeysImage; text: hifi.glyphs.walletKey; @@ -320,8 +326,9 @@ Item { height: 40; onClicked: { - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); removeHmdContainer.visible = true; removeHmdContainerTimer.start(); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 898cdf0ef2..0075e86bdc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -30,6 +30,7 @@ Item { property string lastPage; property bool hasShownSecurityImageTip: false; property string referrer; + property string keyFilePath; Image { anchors.fill: parent; @@ -58,7 +59,7 @@ Item { } onKeyFilePathIfExistsResult: { - keyFilePath.text = path; + root.keyFilePath = path; } } @@ -608,7 +609,7 @@ Item { anchors.fill: parent; RalewaySemiBold { - id: keyFilePathText; + id: keyFilePathHelperText; text: "Private Key File Location:"; size: 18; anchors.top: parent.top; @@ -627,7 +628,7 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.left: parent.left; anchors.leftMargin: 30; - anchors.top: keyFilePathText.bottom; + anchors.top: keyFilePathHelperText.bottom; anchors.topMargin: 8; height: 24; width: height; @@ -643,11 +644,12 @@ Item { } onClicked: { - Qt.openUrlExternally("file:///" + keyFilePath.text.substring(0, keyFilePath.text.lastIndexOf('/'))); + Qt.openUrlExternally("file:///" + keyFilePath.substring(0, keyFilePath.lastIndexOf('/'))); } } RalewayRegular { - id: keyFilePath; + id: keyFilePathText; + text: root.keyFilePath; size: 18; anchors.top: clipboardButton.top; anchors.left: clipboardButton.right; @@ -670,7 +672,7 @@ Item { id: openInstructionsButton; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; - anchors.top: keyFilePath.bottom; + anchors.top: keyFilePathText.bottom; anchors.topMargin: 30; anchors.left: parent.left; anchors.leftMargin: 30; @@ -682,8 +684,9 @@ Item { instructions01Container.visible = false; instructions02Container.visible = true; keysReadyPageFinishButton.visible = true; - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); } } } diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fc16b85439..fe73adf487 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -41,6 +41,7 @@ #endif static const char* KEY_FILE = "hifikey"; +static const char* INSTRUCTIONS_FILE = "backup_instructions.html"; static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; @@ -104,6 +105,38 @@ RSA* readKeys(const char* filename) { return key; } +bool writeBackupInstructions() { + QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); + QString filename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile outputFile(filename); + bool retval = false; + + if (QFile::exists(filename)) + { + QFile::remove(filename); + } + QFile::copy(inputFilename, filename); + + if (QFile::exists(filename) && outputFile.open(QIODevice::ReadWrite)) { + + QByteArray fileData = outputFile.readAll(); + QString text(fileData); + + text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); + + outputFile.seek(0); // go to the beginning of the file + outputFile.write(text.toUtf8()); // write the new text back to the file + + outputFile.close(); // close the file handle. + + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << filename; + } + return retval; +} + bool writeKeys(const char* filename, RSA* keys) { FILE* fp; bool retval = false; @@ -121,6 +154,8 @@ bool writeKeys(const char* filename, RSA* keys) { QFile(QString(filename)).remove(); return retval; } + + writeBackupInstructions(); retval = true; qCDebug(commerce) << "wrote keys successfully"; @@ -488,7 +523,6 @@ bool Wallet::generateKeyPair() { // TODO: redo this soon -- need error checking and so on writeSecurityImage(_securityImage, keyFilePath()); - emit keyFilePathIfExistsResult(getKeyFilePath()); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); _publicKeys.push_back(key); @@ -646,6 +680,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(QString(keyFilePath())).remove(); QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; + emit keyFilePathIfExistsResult(getKeyFilePath()); return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; From b81a8a95eab181f794929c2ee9e1baee41435b72 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 9 Oct 2017 15:23:22 -0700 Subject: [PATCH 4/5] Final fixes --- .../resources/qml/hifi/commerce/checkout/Checkout.qml | 6 +++--- interface/src/Application.cpp | 9 ++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index e2ecf927a5..8d94e284ed 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -115,7 +115,7 @@ Rectangle { } onItemHrefChanged: { - itemIsJson = root.itemHref.indexOf('.json') !== -1; + itemIsJson = root.itemHref.endsWith('.json'); } onItemPriceChanged: { @@ -575,8 +575,8 @@ Rectangle { anchors.right: parent.right; text: "Rez It" onClicked: { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); } rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0fc8c46cdc..504e91ff58 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2689,15 +2689,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) - // if they come from the High Fidelity Marketplace Assets CDN QUrl jsonURL { urlString }; - if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { - emit svoImportRequested(urlString); - return true; - } else { - return false; - } + emit svoImportRequested(urlString); + return true; } bool Application::importSVOFromURL(const QString& urlString) { From 8d0d9a159fd35514777f8e22af700662bde1d595 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Oct 2017 10:38:19 -0700 Subject: [PATCH 5/5] Bugfixes --- .../qml/hifi/commerce/wallet/Help.qml | 2 +- interface/src/commerce/Wallet.cpp | 5 + scripts/system/html/js/marketplacesInject.js | 109 ++++++++++-------- scripts/system/marketplaces/marketplaces.js | 25 ++-- 4 files changed, 80 insertions(+), 61 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index fb0fd7a76e..65c06994f8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -105,7 +105,7 @@ Item { ListElement { isExpanded: false; question: "What is a 'Security Pic'?" - answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The Pic is stored on your hard drive inside the same file as your private keys."); + answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The encrypted Pic is stored on your hard drive inside the same file as your private keys."); } ListElement { isExpanded: false; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index fe73adf487..d7227a58f7 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -338,6 +338,11 @@ Wallet::Wallet() { walletScriptingInterface->setWalletStatus(status); emit walletStatusResult(status); }); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { + getWalletStatus(); + }); } Wallet::~Wallet() { diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 41db724c3f..fc16eae8bf 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -26,7 +26,7 @@ var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free + var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; @@ -99,7 +99,9 @@ } function maybeAddSetupWalletButton() { - if (userIsLoggedIn && walletNeedsSetup) { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + var resultsElement = document.getElementById('results'); var setupWalletElement = document.createElement('div'); setupWalletElement.classList.add("row"); @@ -135,7 +137,8 @@ } function maybeAddLogInButton() { - if (!userIsLoggedIn) { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); var resultsElement = document.getElementById('results'); var logInElement = document.createElement('div'); logInElement.classList.add("row"); @@ -300,68 +303,72 @@ } function injectHiFiCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); maybeAddSetupWalletButton(); - changeDropdownMenu(); - var target = document.getElementById('templated-items'); - // MutationObserver is necessary because the DOM is populated after the page is loaded. - // We're searching for changes to the element whose ID is '#templated-items' - this is - // the element that gets filled in by the AJAX. - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - injectBuyButtonOnMainPage(); + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); }); - //observer.disconnect(); - }); - var config = { attributes: true, childList: true, characterData: true }; - observer.observe(target, config); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); - // Try this here in case it works (it will if the user just pressed the "back" button, - // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage(); - maybeAddPurchasesButton(); + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + maybeAddPurchasesButton(); + } } } function injectHiFiItemPageCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); - changeDropdownMenu(); - var purchaseButton = $('#side-info').find('.btn').first(); + if (!$('body').hasClass("code-injected")) { - var href = purchaseButton.attr('href'); - purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + $('body').addClass("code-injected"); + changeDropdownMenu(); - var cost = $('.item-cost').text(); + var purchaseButton = $('#side-info').find('.btn').first(); - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { - purchaseButton.html('PURCHASE ' + cost); + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href); + }); + maybeAddPurchasesButton(); } - - purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); - maybeAddPurchasesButton(); } } @@ -622,7 +629,7 @@ if (parsedJsonMessage.type === "marketplaces") { if (parsedJsonMessage.action === "commerceSetting") { - confirmAllPurchases = !!parsedJsonMessage.data.commerceMode; + commerceMode = !!parsedJsonMessage.data.commerceMode; userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; injectCode(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index deeae0d299..bf9822ba19 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -98,6 +98,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + Wallet.refreshWalletStatus(); var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); showMarketplace(); @@ -157,11 +158,24 @@ } } + function sendCommerceSettings() { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", false), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1 + } + })); + } + marketplaceButton.clicked.connect(onClick); tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.walletStatusChanged.connect(sendCommerceSettings); Wallet.refreshWalletStatus(); function onMessage(message) { @@ -203,15 +217,7 @@ canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified }); } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", false), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1 - } - })); + sendCommerceSettings(); } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; @@ -244,6 +250,7 @@ tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); });