WalletScriptingInterface; wallet status refactor

This commit is contained in:
Zach Fox 2017-09-29 12:14:20 -07:00
parent a1ae13489e
commit 63396a2fc3
13 changed files with 242 additions and 152 deletions

View file

@ -29,7 +29,6 @@ Rectangle {
property string activeView: "initialize";
property bool purchasesReceived: false;
property bool balanceReceived: false;
property bool securityImageResultReceived: false;
property string itemId;
property string itemPreviewImageUrl;
property string itemHref;
@ -45,47 +44,30 @@ Rectangle {
Hifi.QmlCommerce {
id: commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
if (root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
}
} else if (walletStatus === 1) {
if (root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
notSetUpTimer.start();
}
} else if (walletStatus === 2) {
if (root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
}
} else if (walletStatus === 3) {
authSuccessStep();
} else {
console.log("ERROR in Checkout.qml: Unknown wallet status: " + walletStatus);
}
}
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.account();
}
}
onAccountResult: {
if (result.status === "success") {
commerce.getKeyFilePathIfExists();
} else {
// unsure how to handle a failure here. We definitely cannot proceed.
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
notSetUpTimer.start();
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
onSecurityImageResult: {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
notSetUpTimer.start();
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
}
}
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
} else if (isAuthenticated) {
authSuccessStep();
}
}
@ -197,10 +179,9 @@ Rectangle {
color: hifi.colors.white;
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
balanceReceived = false;
commerce.getLoginStatus();
commerce.getWalletStatus();
}
}

View file

@ -34,17 +34,19 @@ Item {
Hifi.QmlCommerce {
id: commerce;
onLoginStatusResult: {
if (!isLoggedIn) {
onWalletStatusResult: {
if (walletStatus === 0) {
sendToParent({method: "needsLogIn"});
} else if (walletStatus === 3) {
commerce.getSecurityImage();
} else {
console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus);
}
}
onAccountResult: {
if (result.status === "success") {
commerce.getKeyFilePathIfExists();
} else {
// unsure how to handle a failure here. We definitely cannot proceed.
onLoginStatusResult: {
if (!isLoggedIn) {
sendToParent({method: "needsLogIn"});
}
}
@ -57,8 +59,7 @@ Item {
}
Component.onCompleted: {
commerce.getLoginStatus();
commerce.getSecurityImage();
commerce.getWalletStatus();
}
Connections {

View file

@ -41,47 +41,35 @@ Rectangle {
Hifi.QmlCommerce {
id: commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
if (root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
}
} else if (walletStatus === 1) {
if (root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
notSetUpTimer.start();
}
} else if (walletStatus === 2) {
if (root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
}
} else if (walletStatus === 3) {
if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
root.activeView = "firstUseTutorial";
} else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") {
root.activeView = "purchasesMain";
commerce.inventory();
}
} else {
console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus);
}
}
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.account();
}
}
onAccountResult: {
if (result.status === "success") {
commerce.getKeyFilePathIfExists();
} else {
// unsure how to handle a failure here. We definitely cannot proceed.
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
notSetUpTimer.start();
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
onSecurityImageResult: {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
notSetUpTimer.start();
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
}
}
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
} else if (isAuthenticated) {
sendToScript({method: 'purchases_getIsFirstUse'});
}
}
@ -205,7 +193,7 @@ Rectangle {
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
commerce.getLoginStatus();
commerce.getWalletStatus();
}
}
@ -241,7 +229,7 @@ Rectangle {
onSendSignalToParent: {
if (msg.method === "authSuccess") {
root.activeView = "initialize";
sendToScript({method: 'purchases_getIsFirstUse'});
commerce.getWalletStatus();
} else {
sendToScript(msg);
}
@ -260,7 +248,7 @@ Rectangle {
switch (message.method) {
case 'tutorial_skipClicked':
case 'tutorial_finished':
sendToScript({method: 'purchases_setIsFirstUse'});
Settings.setValue("isFirstUseOfPurchases", false);
root.activeView = "purchasesMain";
commerce.inventory();
break;
@ -679,14 +667,6 @@ Rectangle {
titleBarContainer.referrerURL = message.referrerURL;
filterBar.text = message.filterText ? message.filterText : "";
break;
case 'purchases_getIsFirstUseResult':
if ((message.isFirstUseOfPurchases || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") {
root.activeView = "firstUseTutorial";
} else if (!message.isFirstUseOfPurchases && root.activeView === "initialize") {
root.activeView = "purchasesMain";
commerce.inventory();
}
break;
case 'inspectionCertificate_setMarketplaceId':
case 'inspectionCertificate_setItemInfo':
inspectionCertificate.fromScript(message);

View file

@ -38,48 +38,39 @@ Rectangle {
Hifi.QmlCommerce {
id: commerce;
onWalletStatusResult: {
if (walletStatus === 0) {
if (root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
}
} else if (walletStatus === 1) {
if (root.activeView !== "walletSetup") {
root.activeView = "walletSetup";
}
} else if (walletStatus === 2) {
if (root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
}
} else if (walletStatus === 3) {
root.activeView = "walletHome";
commerce.getSecurityImage();
} else {
console.log("ERROR in Wallet.qml: Unknown wallet status: " + walletStatus);
}
}
onLoginStatusResult: {
if (!isLoggedIn && root.activeView !== "needsLogIn") {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.account();
}
}
onAccountResult: {
if (result.status === "success") {
commerce.getKeyFilePathIfExists();
} else {
// unsure how to handle a failure here. We definitely cannot proceed.
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "walletSetup") {
root.activeView = "walletSetup";
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
onSecurityImageResult: {
if (!exists && root.activeView !== "walletSetup") { // "If security image is not set up"
root.activeView = "walletSetup";
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
if (exists) {
titleBarSecurityImage.source = "";
titleBarSecurityImage.source = "image://security/securityImage";
}
}
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && passphraseModal && root.activeView !== "passphraseModal") {
root.activeView = "passphraseModal";
} else if (isAuthenticated) {
root.activeView = "walletHome";
}
}
}
SecurityImageModel {
@ -179,7 +170,7 @@ Rectangle {
if (msg.method === 'walletSetup_finished') {
if (msg.referrer === '') {
root.activeView = "initialize";
commerce.getLoginStatus();
commerce.getWalletStatus();
} else if (msg.referrer === 'purchases') {
sendToScript({method: 'goToPurchases'});
}
@ -254,7 +245,7 @@ Rectangle {
color: hifi.colors.baseGray;
Component.onCompleted: {
commerce.getLoginStatus();
commerce.getWalletStatus();
}
}

View file

@ -167,6 +167,7 @@
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#include "scripting/SelectionScriptingInterface.h"
#include "scripting/WalletScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
@ -683,6 +684,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<ContextOverlayInterface>();
DependencyManager::set<Ledger>();
DependencyManager::set<Wallet>();
DependencyManager::set<WalletScriptingInterface>();
DependencyManager::set<FadeEffect>();
@ -2328,6 +2330,7 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
@ -6080,6 +6083,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
scriptEngine->registerGlobalObject("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);

View file

@ -15,6 +15,7 @@
#include "Ledger.h"
#include "Wallet.h"
#include <AccountManager.h>
#include "scripting/WalletScriptingInterface.h"
HIFI_QML_DEF(QmlCommerce)
@ -30,13 +31,41 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
}
void QmlCommerce::getWalletStatus() {
auto wallet = DependencyManager::get<Wallet>();
auto ledger = DependencyManager::get<Ledger>();
auto walletScriptingInterface = DependencyManager::get<WalletScriptingInterface>();
uint status;
if (DependencyManager::get<AccountManager>()->isLoggedIn()) {
// This will set account info for the wallet, allowing us to decrypt and display the security image.
ledger->account();
} else {
status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN;
emit walletStatusResult(status);
walletScriptingInterface->setWalletStatus(status);
return;
}
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);
}
void QmlCommerce::getLoginStatus() {
emit loginStatusResult(DependencyManager::get<AccountManager>()->isLoggedIn());
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
wallet->sendKeyFilePathIfExists();
emit keyFilePathIfExistsResult(wallet->getKeyFilePath());
}
void QmlCommerce::getWalletAuthenticatedStatus() {

View file

@ -27,7 +27,16 @@ 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);
void loginStatusResult(bool isLoggedIn);
void keyFilePathIfExistsResult(const QString& path);
void securityImageResult(bool exists);
@ -42,6 +51,8 @@ signals:
void accountResult(QJsonObject result);
protected:
Q_INVOKABLE void getWalletStatus();
Q_INVOKABLE void getLoginStatus();
Q_INVOKABLE void getKeyFilePathIfExists();
Q_INVOKABLE void getSecurityImage();

View file

@ -468,7 +468,7 @@ bool Wallet::generateKeyPair() {
// TODO: redo this soon -- need error checking and so on
writeSecurityImage(_securityImage, keyFilePath());
sendKeyFilePathIfExists();
emit keyFilePathIfExistsResult(getKeyFilePath());
QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last();
QString key = keyPair.first->toBase64();
_publicKeys.push_back(key);
@ -559,14 +559,14 @@ void Wallet::chooseSecurityImage(const QString& filename) {
emit securityImageResult(success);
}
void Wallet::getSecurityImage() {
bool Wallet::getSecurityImage() {
unsigned char* data;
int dataLen;
// if already decrypted, don't do it again
if (_securityImage) {
emit securityImageResult(true);
return;
return true;
}
bool success = false;
@ -585,14 +585,15 @@ void Wallet::getSecurityImage() {
success = true;
}
emit securityImageResult(success);
return success;
}
void Wallet::sendKeyFilePathIfExists() {
QString Wallet::getKeyFilePath() {
QString filePath(keyFilePath());
QFileInfo fileInfo(filePath);
if (fileInfo.exists()) {
emit keyFilePathIfExistsResult(filePath);
return filePath;
} else {
emit keyFilePathIfExistsResult("");
return "";
}
}

View file

@ -32,8 +32,8 @@ public:
QStringList listPublicKeys();
QString signWithKey(const QByteArray& text, const QString& key);
void chooseSecurityImage(const QString& imageFile);
void getSecurityImage();
void sendKeyFilePathIfExists();
bool getSecurityImage();
QString getKeyFilePath();
void setSalt(const QByteArray& salt) { _salt = salt; }
QByteArray getSalt() { return _salt; }

View file

@ -0,0 +1,15 @@
//
// WalletScriptingInterface.cpp
// interface/src/scripting
//
// Created by Zach Fox on 2017-09-29.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "WalletScriptingInterface.h"
WalletScriptingInterface::WalletScriptingInterface() {
}

View file

@ -0,0 +1,37 @@
// WalletScriptingInterface.h
// interface/src/scripting
//
// Created by Zach Fox on 2017-09-29.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_WalletScriptingInterface_h
#define hifi_WalletScriptingInterface_h
#include <QtCore/QObject>
#include <DependencyManager.h>
class WalletScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(uint walletStatus READ getWalletStatus WRITE setWalletStatus NOTIFY walletStatusChanged)
public:
WalletScriptingInterface();
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
void setWalletStatus(const uint& status) { _walletStatus = status; }
signals:
void walletStatusChanged();
private:
uint _walletStatus;
};
#endif // hifi_WalletScriptingInterface_h

View file

@ -28,6 +28,7 @@
var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free
var userIsLoggedIn = false;
var walletNeedsSetup = false;
function injectCommonCode(isDirectoryPage) {
@ -91,6 +92,48 @@
});
}
emitWalletSetupEvent = function() {
EventBridge.emitWebEvent(JSON.stringify({
type: "WALLET_SETUP"
}));
}
function maybeAddSetupWalletButton() {
if (userIsLoggedIn && walletNeedsSetup) {
var resultsElement = document.getElementById('results');
var setupWalletElement = document.createElement('div');
setupWalletElement.classList.add("row");
setupWalletElement.id = "setupWalletDiv";
setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" +
"background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;";
var span = document.createElement('span');
span.style = "margin:10px 5px;color:#1b6420;font-size:15px;";
span.innerHTML = "<a href='#' onclick='emitWalletSetupEvent(); return false;'>Setup your Wallet</a> to get money and shop in Marketplace.";
var xButton = document.createElement('a');
xButton.id = "xButton";
xButton.setAttribute('href', "#");
xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;";
xButton.innerHTML = "X";
xButton.onclick = function () {
setupWalletElement.remove();
dummyRow.remove();
};
setupWalletElement.appendChild(span);
setupWalletElement.appendChild(xButton);
resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild);
// Dummy row for padding
var dummyRow = document.createElement('div');
dummyRow.classList.add("row");
dummyRow.style = "height:15px;";
resultsElement.insertBefore(dummyRow, resultsElement.firstChild);
}
}
function maybeAddLogInButton() {
if (!userIsLoggedIn) {
var resultsElement = document.getElementById('results');
@ -240,6 +283,7 @@
$('body').addClass("code-injected");
maybeAddLogInButton();
maybeAddSetupWalletButton();
var target = document.getElementById('templated-items');
// MutationObserver is necessary because the DOM is populated after the page is loaded.
@ -267,6 +311,7 @@
$('body').addClass("code-injected");
maybeAddLogInButton();
maybeAddSetupWalletButton();
var purchaseButton = $('#side-info').find('.btn').first();
@ -555,7 +600,8 @@
if (parsedJsonMessage.type === "marketplaces") {
if (parsedJsonMessage.action === "commerceSetting") {
confirmAllPurchases = !!parsedJsonMessage.data.commerceMode;
userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn
userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn;
walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup;
injectCode();
}
}

View file

@ -202,7 +202,8 @@
action: "commerceSetting",
data: {
commerceMode: Settings.getValue("commerce", false),
userIsLoggedIn: Account.loggedIn
userIsLoggedIn: Account.loggedIn,
walletNeedsSetup: Wallet.walletStatus !== 3
}
}));
} else if (parsedJsonMessage.type === "PURCHASES") {
@ -211,6 +212,8 @@
tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
} else if (parsedJsonMessage.type === "LOGIN") {
openLoginWindow();
} else if (parsedJsonMessage.type === "WALLET_SETUP") {
tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH);
}
}
}
@ -324,15 +327,6 @@
case 'maybeEnableHmdPreview':
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
case 'purchases_getIsFirstUse':
tablet.sendToQml({
method: 'purchases_getIsFirstUseResult',
isFirstUseOfPurchases: Settings.getValue("isFirstUseOfPurchases", true)
});
break;
case 'purchases_setIsFirstUse':
Settings.setValue("isFirstUseOfPurchases", false);
break;
case 'purchases_openGoTo':
tablet.loadQMLSource("TabletAddressDialog.qml");
break;