diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 786e045..b5f878f 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -100,8 +100,21 @@ AppContext::AppContext(QCommandLineParser *cmdargs) { connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged); connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes); - // Tor & socks proxy - this->ws = new WSClient(this, wsUrl); + // init backend URLs + if(cmdargs->isSet("backend-host")) + this->backendHost = cmdargs->value("backend-host"); + if(cmdargs->isSet("backend-host")) + this->backendPort = cmdargs->value("backend-port").toUInt(); + if(cmdargs->isSet("backend-tls")) + this->backendTLS = true; + + backendWSUrl = this->backendTLS ? "wss://" : "ws://"; + backendWSUrl += QString("%1:%2").arg(this->backendHost).arg(this->backendPort); + backendHTTPUrl = this->backendTLS ? "https://" : "http://"; + backendHTTPUrl += QString("%1:%2").arg(this->backendHost).arg(this->backendPort); + + // init websocket client + this->ws = new WSClient(this); connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage); connect(this->ws, &WSClient::connectionEstablished, this, &AppContext::wsConnected); connect(this->ws, &WSClient::closed, this, &AppContext::wsDisconnected); @@ -177,7 +190,8 @@ void AppContext::initTor() { this->tor = new Tor(this, this); this->tor->start(); - if (!isWhonix && wsUrl.contains(".onion")) { + if (!isWhonix && backendHost.contains(".onion")) { + qDebug() << "'backend-host' did not contain '.onion' - running without Tor proxy."; this->networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort); this->network->setProxy(*networkProxy); this->ws->webSocket.setProxy(*networkProxy); @@ -421,7 +435,10 @@ void AppContext::onWSMessage(const QJsonObject &msg) { if(changed) emit blockHeightWSUpdated(this->heights); } - + else if(cmd == "yellwow") { + this->yellowPagesData = msg.value("data").toArray(); + emit yellowUpdated(); + } else if(cmd == "rpc_nodes") { this->onWSNodes(msg.value("data").toArray()); } diff --git a/src/appcontext.h b/src/appcontext.h index b07444a..9e9f99c 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -66,7 +66,13 @@ public: QString defaultWalletDir; QString defaultWalletDirRoot; QString tmpTxDescription; - QString wsUrl = "6wku2m4zrv6j666crlo7lzofv6ud6enzllyhou3ijeigpukymi37caad.onion"; + + // https://git.wownero.com/wowlet/wowlet-backend/ + QString backendHost = "6wku2m4zrv6j666crlo7lzofv6ud6enzllyhou3ijeigpukymi37caad.onion"; + unsigned int backendPort = 80; + bool backendTLS = false; + QString backendWSUrl; + QString backendHTTPUrl; QString walletPath; QString walletPassword = ""; @@ -106,6 +112,7 @@ public: static QMap txDescriptionCache; static QMap txCache; static TxFiatHistory *txFiatHistory; + QJsonArray yellowPagesData; QJsonObject versionPending; // libwalletqt @@ -205,6 +212,7 @@ signals: void nodesUpdated(QList> &nodes); void ccsUpdated(QList> &entries); void suchWowUpdated(const QJsonArray &such_data); + void yellowUpdated(); void nodeSourceChanged(NodeSource nodeSource); void XMRigDownloads(const QJsonObject &data); void pinLookupReceived(QString address, QString pin); diff --git a/src/assets/images/welcome/wow20.png b/src/assets/images/welcome/wow20.png index de0f277..77b4847 100644 Binary files a/src/assets/images/welcome/wow20.png and b/src/assets/images/welcome/wow20.png differ diff --git a/src/contactswidget.cpp b/src/contactswidget.cpp index f3e048c..dc59f7e 100644 --- a/src/contactswidget.cpp +++ b/src/contactswidget.cpp @@ -32,17 +32,22 @@ ContactsWidget::ContactsWidget(QWidget *parent) : this->newContact(); }); - // row context menu - m_rowMenu = new QMenu(ui->contacts); - m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress); - m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName); - m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo); - m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact); - connect(ui->contacts, &QTreeView::customContextMenuRequested, [=](const QPoint & point){ QModelIndex index = ui->contacts->indexAt(point); if (index.isValid()) { + auto username = index.model()->data(index.siblingAtColumn(AddressBookModel::Description), Qt::UserRole).toString(); + + m_rowMenu = new QMenu(ui->contacts); + if(username.contains("(YellWOWPages)")) + m_rowMenu->addAction(QIcon(":/assets/images/network.png"), "Visit user's YellWOWPage", this, &ContactsWidget::visitYellowPage); + + m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ContactsWidget::copyAddress); + m_rowMenu->addAction(QIcon(":/assets/images/copy.png"), "Copy name", this, &ContactsWidget::copyName); + m_rowMenu->addAction("Pay to", this, &ContactsWidget::payTo); + m_rowMenu->addAction("Delete", this, &ContactsWidget::deleteContact); + m_rowMenu->exec(ui->contacts->viewport()->mapToGlobal(point)); + m_rowMenu->deleteLater(); } else { m_contextMenu->exec(ui->contacts->viewport()->mapToGlobal(point)); @@ -52,6 +57,68 @@ ContactsWidget::ContactsWidget(QWidget *parent) : connect(ui->search, &QLineEdit::textChanged, this, &ContactsWidget::setSearchFilter); } +QMap ContactsWidget::data() { + auto rtn = QMap(); + for (int i = 0; i < m_ctx->currentWallet->addressBook()->count(); i++) { + m_ctx->currentWallet->addressBook()->getRow(i, [&rtn](const AddressBookInfo &entry) { + rtn[entry.description()] = entry.address(); + }); + } + return rtn; +} + +unsigned int ContactsWidget::rowIndex(const QString &name) { + // name -> row index lookup + int result = -1; + for (int i = 0; i < m_ctx->currentWallet->addressBook()->count(); i++) { + m_ctx->currentWallet->addressBook()->getRow(i, [i, name, &result](const AddressBookInfo &entry) { + if(entry.description() == name) result = i; + return; + }); + + if(result != -1) + return result; + } + return result; +} + +void ContactsWidget::loadYellowPages() { + if (m_ctx->currentWallet == nullptr || m_ctx->yellowPagesData.empty()) + return; + + auto contacts = this->data(); + for (auto item: m_ctx->yellowPagesData) { + auto obj = item.toObject(); + const auto username = QString("%1 (YellWOWPages)").arg(obj.value("username").toString()); + const auto address = obj.value("address").toString(); + + if(contacts.contains(username)) { + if(contacts[username] == address) continue; + + // update the address + auto idx = this->rowIndex(username); + if(idx == -1) continue; + m_model->deleteRow((int)idx); + } + + bool addressValid = WalletManager::addressValid(address, m_ctx->currentWallet->nettype()); + if (!addressValid) { + continue; + } + + m_ctx->currentWallet->addressBook()->addRow(address, "", username); + } +} + +void ContactsWidget::visitYellowPage() { + auto index = ui->contacts->currentIndex(); + auto username = index.model()->data( + index.siblingAtColumn(AddressBookModel::Description), + Qt::UserRole).toString(); + username = username.replace(" (YellWOWPages)", "").trimmed(); + Utils::externalLinkWarning(this, QString("https://yellow.wownero.com/user/%1").arg(username)); +} + void ContactsWidget::copyAddress() { QModelIndex index = ui->contacts->currentIndex(); ModelUtils::copyColumn(&index, AddressBookModel::Address); diff --git a/src/contactswidget.h b/src/contactswidget.h index 4035773..ac41a32 100644 --- a/src/contactswidget.h +++ b/src/contactswidget.h @@ -22,6 +22,7 @@ class ContactsWidget : public QWidget public: explicit ContactsWidget(QWidget *parent = nullptr); void setModel(AddressBookModel * model); + QMap data(); ~ContactsWidget() override; public slots: @@ -30,9 +31,11 @@ public slots: void payTo(); void newContact(QString address = "", QString name = ""); void deleteContact(); + void visitYellowPage(); void setShowFullAddresses(bool show); void setSearchFilter(const QString &filter); void resetModel(); + void loadYellowPages(); signals: void fillAddress(QString &address); @@ -50,6 +53,8 @@ private: QMenu *m_headerMenu; AddressBookModel * m_model; AddressBookProxyModel * m_proxyModel; + + unsigned int rowIndex(const QString &name); }; #endif // CONTACTSWIDGET_H diff --git a/src/dialog/debuginfodialog.cpp b/src/dialog/debuginfodialog.cpp index f1aeed0..409c242 100644 --- a/src/dialog/debuginfodialog.cpp +++ b/src/dialog/debuginfodialog.cpp @@ -50,6 +50,7 @@ void DebugInfoDialog::updateInfo() { ui->label_synchronized->setText(m_ctx->currentWallet->synchronized() ? "True" : "False"); auto node = m_ctx->nodes->connection(); + ui->label_websocketURL->setText(m_ctx->backendWSUrl); ui->label_remoteNode->setText(node.full); ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus())); ui->label_torStatus->setText(torStatus); @@ -101,6 +102,7 @@ void DebugInfoDialog::copyToClipboad() { text += QString("Remote node: %1 \n").arg(ui->label_remoteNode->text()); text += QString("Wallet status: %1 \n").arg(ui->label_walletStatus->text()); text += QString("Tor status: %1 \n").arg(ui->label_torStatus->text()); + text += QString("Websocket URL: %1 \n").arg(ui->label_websocketURL->text()); text += QString("Websocket status: %1 \n").arg(ui->label_websocketStatus->text()); text += QString("Network type: %1 \n").arg(ui->label_netType->text()); diff --git a/src/dialog/debuginfodialog.ui b/src/dialog/debuginfodialog.ui index 73f7352..f036447 100644 --- a/src/dialog/debuginfodialog.ui +++ b/src/dialog/debuginfodialog.ui @@ -183,14 +183,14 @@ - + Websocket status: - + TextLabel @@ -200,21 +200,21 @@ - + Qt::Horizontal - + Network type: - + TextLabel @@ -224,14 +224,14 @@ - + Seed type: - + TextLabel @@ -241,14 +241,14 @@ - + View only: - + TextLabel @@ -258,14 +258,14 @@ - + Timestamp: - + TextLabel @@ -275,14 +275,14 @@ - + Operating system: - + TextLabel @@ -292,7 +292,7 @@ - + Qt::Horizontal @@ -316,20 +316,34 @@ - + TextLabel - + Primary only: + + + + Websocket URL: + + + + + + + TextLabel + + + diff --git a/src/main.cpp b/src/main.cpp index 4047355..02a5047 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,6 +107,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QCommandLineOption androidDebugOption(QStringList() << "android-debug", "Start the Android interface without actually running on Android - for debugging purposes. Requires -DANDROID_DEBUG=ON CMake definition."); parser.addOption(androidDebugOption); + QCommandLineOption backendHostOption(QStringList() << "backend-host", "specify your own `wowlet-backend` host", "backend-host"); + parser.addOption(backendHostOption); + + QCommandLineOption backendPortOption(QStringList() << "backend-port", "specify your own `wowlet-backend` port", "backend-port"); + parser.addOption(backendPortOption); + + QCommandLineOption backendTLS(QStringList() << "backend-tls", "`wowlet-backend` is running via TLS? 'wss://' and 'https://' will be used.", "backend-tls"); + parser.addOption(backendTLS); + auto parsed = parser.parse(argv_); if(!parsed) { qCritical() << parser.errorText(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9c96079..431caef 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -303,6 +303,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) : // Contacts connect(ui->contactWidget, &ContactsWidget::fillAddress, ui->sendWidget, &SendWidget::fillAddress); + connect(m_ctx, &AppContext::yellowUpdated, ui->contactWidget, &ContactsWidget::loadYellowPages); // Open alias connect(ui->sendWidget, &SendWidget::resolveOpenAlias, m_ctx, &AppContext::onOpenAliasResolve); @@ -632,6 +633,7 @@ void MainWindow::onWalletOpened(Wallet *wallet) { // contacts widget ui->contactWidget->setModel(m_ctx->currentWallet->addressBookModel()); + ui->contactWidget->loadYellowPages(); // coins page m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount()); diff --git a/src/utils/wsclient.cpp b/src/utils/wsclient.cpp index b5c0204..3ae1a4d 100644 --- a/src/utils/wsclient.cpp +++ b/src/utils/wsclient.cpp @@ -8,18 +8,18 @@ #include "wsclient.h" #include "appcontext.h" -WSClient::WSClient(AppContext *ctx, const QString &url, QObject *parent) : +WSClient::WSClient(AppContext *ctx, QObject *parent) : QObject(parent), m_ctx(ctx) { + // this class connects to `https://git.wownero.com/wowlet/wowlet-backend/` connect(&this->webSocket, &QWebSocket::binaryMessageReceived, this, &WSClient::onbinaryMessageReceived); connect(&this->webSocket, &QWebSocket::connected, this, &WSClient::onConnected); connect(&this->webSocket, &QWebSocket::disconnected, this, &WSClient::closed); connect(&this->webSocket, QOverload::of(&QWebSocket::error), this, &WSClient::onError); - m_tor = url.contains(".onion"); - this->url = QString("ws://%1/ws").arg(url); + m_tor = m_ctx->backendHost.contains(".onion"); - // Keep websocket connection alive + // keep-alive connect(&m_pingTimer, &QTimer::timeout, [this]{ if (this->webSocket.state() == QAbstractSocket::ConnectedState) this->webSocket.ping(); @@ -40,7 +40,7 @@ void WSClient::start() { qDebug() << "WebSocket connect:" << url.url(); #endif if((m_tor && this->m_ctx->tor->torConnected) || !m_tor) - this->webSocket.open(QUrl(this->url)); + this->webSocket.open(QString("%1/ws").arg(m_ctx->backendWSUrl)); if(!this->m_connectionTimer.isActive()) { connect(&this->m_connectionTimer, &QTimer::timeout, this, &WSClient::checkConnection); diff --git a/src/utils/wsclient.h b/src/utils/wsclient.h index 90b6f75..50efa0c 100644 --- a/src/utils/wsclient.h +++ b/src/utils/wsclient.h @@ -14,11 +14,10 @@ class WSClient : public QObject Q_OBJECT public: - explicit WSClient(AppContext *ctx, const QString &url, QObject *parent = nullptr); + explicit WSClient(AppContext *ctx, QObject *parent = nullptr); void start(); void sendMsg(const QByteArray &data); QWebSocket webSocket; - QString url; signals: void closed(); diff --git a/src/widgets/suchwowwidget.cpp b/src/widgets/suchwowwidget.cpp index a8a2322..27af675 100644 --- a/src/widgets/suchwowwidget.cpp +++ b/src/widgets/suchwowwidget.cpp @@ -19,7 +19,7 @@ SuchWowPost::SuchWowPost(AppContext *ctx, QObject *parent) : m_ctx(ctx) { m_networkImg = new UtilsNetworking(m_ctx->network, this); m_networkThumb = new UtilsNetworking(m_ctx->network, this); - m_weburl = QString("http://%1/suchwow").arg(this->m_ctx->wsUrl); + m_weburl = QString("%1/suchwow").arg(this->m_ctx->backendHTTPUrl); } void SuchWowPost::onThumbReceived(QByteArray data) {