2 changed files with 207 additions and 0 deletions
@ -0,0 +1,171 @@
|
||||
#include "joindialog.h" |
||||
#include "invite_link.h" |
||||
#include "debug_ui.h" |
||||
#include "../transport/utun_node.h" |
||||
#include "../transport/gui_bridge.h" |
||||
#include "../transport/chat_sync.h" |
||||
#include "../db/db_manager.h" |
||||
#include <QVBoxLayout> |
||||
#include <QHBoxLayout> |
||||
#include <QPushButton> |
||||
#include <QTimer> |
||||
#include <QMessageBox> |
||||
#include <QApplication> |
||||
#include <cstring> |
||||
|
||||
extern "C" { |
||||
#include "../../../lib/sha256.h" |
||||
} |
||||
|
||||
JoinDialog::JoinDialog(UtunNode* node, DbManager* db, QWidget* parent) |
||||
: QDialog(parent) |
||||
, m_node(node) |
||||
, m_db(db) |
||||
{ |
||||
setWindowTitle("Join Channel"); |
||||
setMinimumWidth(420); |
||||
setModal(true); |
||||
|
||||
auto *layout = new QVBoxLayout(this); |
||||
layout->setSpacing(12); |
||||
layout->setContentsMargins(20, 16, 20, 16); |
||||
|
||||
auto *titleLabel = new QLabel("Paste invite link:", this); |
||||
titleLabel->setStyleSheet("font-weight: bold; font-size: 13px;"); |
||||
layout->addWidget(titleLabel); |
||||
|
||||
m_linkEdit = new QLineEdit(this); |
||||
m_linkEdit->setPlaceholderText("utun://..."); |
||||
m_linkEdit->setStyleSheet( |
||||
"QLineEdit { border: 1px solid palette(mid); border-radius: 6px;" |
||||
" padding: 8px 12px; font-size: 13px; background: palette(base); }"); |
||||
layout->addWidget(m_linkEdit); |
||||
|
||||
m_statusLabel = new QLabel(this); |
||||
m_statusLabel->setWordWrap(true); |
||||
m_statusLabel->setStyleSheet("color: gray; font-size: 12px; padding: 4px 0;"); |
||||
layout->addWidget(m_statusLabel); |
||||
|
||||
layout->addStretch(); |
||||
|
||||
auto *btnLayout = new QHBoxLayout(); |
||||
btnLayout->setSpacing(8); |
||||
btnLayout->addStretch(); |
||||
|
||||
m_cancelBtn = new QPushButton("Cancel", this); |
||||
m_cancelBtn->setFixedWidth(90); |
||||
m_cancelBtn->setStyleSheet( |
||||
"QPushButton { border: 1px solid palette(mid); border-radius: 4px;" |
||||
" padding: 6px 16px; font-size: 13px; background: palette(button); }" |
||||
"QPushButton:hover { background: palette(light); }"); |
||||
connect(m_cancelBtn, &QPushButton::clicked, this, &QDialog::reject); |
||||
btnLayout->addWidget(m_cancelBtn); |
||||
|
||||
m_connectBtn = new QPushButton("Connect", this); |
||||
m_connectBtn->setFixedWidth(90); |
||||
m_connectBtn->setDefault(true); |
||||
m_connectBtn->setStyleSheet( |
||||
"QPushButton { border: none; border-radius: 4px;" |
||||
" padding: 6px 16px; font-size: 13px; font-weight: bold;" |
||||
" background: #3390EC; color: white; }" |
||||
"QPushButton:hover { background: #2B7ED4; }" |
||||
"QPushButton:disabled { background: palette(mid); color: palette(midlight); }"); |
||||
connect(m_connectBtn, &QPushButton::clicked, this, &JoinDialog::onConnectClicked); |
||||
btnLayout->addWidget(m_connectBtn); |
||||
|
||||
layout->addLayout(btnLayout); |
||||
|
||||
connect(m_linkEdit, &QLineEdit::returnPressed, this, &JoinDialog::onConnectClicked); |
||||
|
||||
gui_bridge_set_connect_result_cb([](uint64_t nodeId, int result) { |
||||
JoinDialog* dlg = qobject_cast<JoinDialog*>(QApplication::activeModalWidget()); |
||||
if (dlg) dlg->onConnectResult(nodeId, result); |
||||
}); |
||||
} |
||||
|
||||
JoinDialog::~JoinDialog() { |
||||
gui_bridge_set_connect_result_cb(nullptr); |
||||
} |
||||
|
||||
void JoinDialog::setStatus(const QString& text, bool isError) { |
||||
if (isError) |
||||
m_statusLabel->setStyleSheet("color: #D0021B; font-size: 12px; padding: 4px 0;"); |
||||
else |
||||
m_statusLabel->setStyleSheet("color: gray; font-size: 12px; padding: 4px 0;"); |
||||
m_statusLabel->setText(text); |
||||
} |
||||
|
||||
void JoinDialog::onConnectClicked() { |
||||
if (m_connecting) return; |
||||
QString link = m_linkEdit->text().trimmed(); |
||||
if (link.isEmpty()) { |
||||
setStatus("Enter an invite link.", true); |
||||
return; |
||||
} |
||||
|
||||
setStatus("Parsing link..."); |
||||
InviteData d = decodeInviteLink(link); |
||||
if (d.channelId.isEmpty() || d.pubkey.size() != 32) { |
||||
setStatus("Invalid invite link.", true); |
||||
return; |
||||
} |
||||
if (d.addrs.isEmpty()) { |
||||
setStatus("No peer addresses in the invite link.", true); |
||||
return; |
||||
} |
||||
|
||||
uint8_t hash[32]; |
||||
SC_SHA256_CTX ctx; |
||||
sc_sha256_init(&ctx); |
||||
sc_sha256_update(&ctx, (const uint8_t*)d.pubkey.constData(), 32); |
||||
sc_sha256_final(&ctx, hash); |
||||
uint64_t nodeId; |
||||
memcpy(&nodeId, hash, sizeof(nodeId)); |
||||
nodeId &= 0x7FFFFFFFFFFFFFFFULL; |
||||
|
||||
m_pendingNodeId = nodeId; |
||||
m_connecting = true; |
||||
m_connectBtn->setEnabled(false); |
||||
m_linkEdit->setReadOnly(true); |
||||
|
||||
setStatus("Connecting to peer..."); |
||||
|
||||
QByteArray addrsBuf; |
||||
for (const auto& a : d.addrs) { |
||||
addrsBuf.append(static_cast<char>(a.family)); |
||||
addrsBuf.append(a.address); |
||||
addrsBuf.append(static_cast<char>((a.port >> 8) & 0xFF)); |
||||
addrsBuf.append(static_cast<char>(a.port & 0xFF)); |
||||
} |
||||
|
||||
QByteArray chId = d.channelId.toUtf8(); |
||||
int addrCount = d.addrs.size(); |
||||
|
||||
chat_sync_connect_from_invite(nodeId, |
||||
(const uint8_t*)d.pubkey.constData(), |
||||
(const uint8_t*)addrsBuf.constData(), addrCount, |
||||
(const uint8_t*)chId.constData(), chId.size()); |
||||
} |
||||
|
||||
void JoinDialog::onConnectResult(uint64_t nodeId, int result) { |
||||
if (!m_connecting || nodeId != m_pendingNodeId) return; |
||||
|
||||
if (result == 0) { |
||||
setStatus("Connected successfully!"); |
||||
QTimer::singleShot(800, this, [this] { accept(); }); |
||||
} else { |
||||
m_connecting = false; |
||||
m_connectBtn->setEnabled(true); |
||||
m_linkEdit->setReadOnly(false); |
||||
m_connectBtn->setFocus(); |
||||
|
||||
const char* errMsg = "Unknown error."; |
||||
switch (result) { |
||||
case -3: errMsg = "Connection timed out."; break; |
||||
case -4: errMsg = "Peer is unreachable."; break; |
||||
case -6: errMsg = "Already connected to this peer."; break; |
||||
default: break; |
||||
} |
||||
setStatus(QString("Connection failed: %1").arg(errMsg), true); |
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@
|
||||
#pragma once |
||||
|
||||
#include <QDialog> |
||||
#include <QLineEdit> |
||||
#include <QLabel> |
||||
#include <QPushButton> |
||||
#include <cstdint> |
||||
|
||||
class UtunNode; |
||||
class DbManager; |
||||
|
||||
class JoinDialog : public QDialog { |
||||
Q_OBJECT |
||||
public: |
||||
explicit JoinDialog(UtunNode* node, DbManager* db, QWidget* parent = nullptr); |
||||
~JoinDialog() override; |
||||
|
||||
signals: |
||||
void joined(const QString& channelId); |
||||
|
||||
private slots: |
||||
void onConnectClicked(); |
||||
|
||||
private: |
||||
void setStatus(const QString& text, bool isError = false); |
||||
void onConnectResult(uint64_t nodeId, int result); |
||||
|
||||
UtunNode* m_node; |
||||
DbManager* m_db; |
||||
QLineEdit* m_linkEdit; |
||||
QLabel* m_statusLabel; |
||||
QPushButton* m_connectBtn; |
||||
QPushButton* m_cancelBtn; |
||||
bool m_connecting = false; |
||||
uint64_t m_pendingNodeId = 0; |
||||
}; |
||||
Loading…
Reference in new issue