Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The result will include:

```bash
# Build only the library (plugin)
nix build '.#logos-chat-module-lib'
nix build '.#lib'

# Build only the generated headers
nix build '.#logos-chat-module-include'
Expand Down
23 changes: 20 additions & 3 deletions chat_interface.h
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
#pragma once

#include <QtCore/QObject>
#include <QtCore/QStringList>
#include "interface.h"
#include <functional>

// Define a callback type for message handling
using MessageCallback = std::function<void(const std::string&, const std::string&, const std::string&)>;

// Discovery mode enum
enum class DiscoveryMode {
ExtKadOnly = 0, // Extended Kademlia only
StdDiscovery = 1, // Rendezvous + Peer Exchange (no discv5)
All = 2 // Kad + Rendezvous + Peer Exchange
};

class ChatInterface : public PluginInterface {
public:
virtual ~ChatInterface() {}

// Core chat functionality
Q_INVOKABLE virtual bool initialize() = 0;
// Initialize with JSON configuration
// configJson: JSON object with fields:
// "mode": int (0=ExtKadOnly, 1=StdDiscovery, 2=All)
// "bootstrapNodes": comma-separated multiaddr strings
// "mixnodes": comma-separated "multiaddr:mixPubKey" strings
// "storeNode": multiaddr of the store node for history retrieval
Q_INVOKABLE virtual bool initialize(const QString& configJson) = 0;
Q_INVOKABLE virtual bool joinChannel(const QString& channelName) = 0;
Q_INVOKABLE virtual void sendMessage(const QString& channelName, const QString& username, const QString& message) = 0;
Q_INVOKABLE virtual bool retrieveHistory(const std::string& channelName) = 0;

// Network metrics
Q_INVOKABLE virtual bool getMixnodePoolSize() = 0;
Q_INVOKABLE virtual bool getLightpushPeersCount() = 0;

signals:
// for now this is required for events, later it might not be necessary if using a proxy
void eventResponse(const QString& eventName, const QVariantList& data);
};

#define ChatInterface_iid "org.logos.ChatInterface"
Q_DECLARE_INTERFACE(ChatInterface, ChatInterface_iid)
Q_DECLARE_INTERFACE(ChatInterface, ChatInterface_iid)
148 changes: 121 additions & 27 deletions chat_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,127 @@
#include <QDateTime>
#include <QDebug>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include "logos_api_client.h"

ChatPlugin::ChatPlugin() : currentRelayTopic("/waku/2/rs/16/32") {
ChatPlugin::ChatPlugin() : currentRelayTopic("")
{
}

ChatPlugin::~ChatPlugin() {
if (logos) {
ChatPlugin::~ChatPlugin()
{
if (logos)
{
delete logos;
logos = nullptr;
}
if (logosAPI) {
if (logosAPI)
{
delete logosAPI;
logosAPI = nullptr;
}
}

bool ChatPlugin::ensureLogosContext(const char* caller) const {
bool ChatPlugin::ensureLogosContext(const char *caller) const
{
const QString context = QString::fromLatin1(caller ? caller : "unknown");

if (!logosAPI) {
if (!logosAPI)
{
qWarning() << "ChatPlugin:" << context << "- LogosAPI not initialized";
return false;
}

if (!logos) {
if (!logos)
{
qWarning() << "ChatPlugin:" << context << "- LogosModules not initialized";
return false;
}

return true;
}

bool ChatPlugin::initialize() {
if (!ensureLogosContext("initialize")) {
bool ChatPlugin::initialize(const QString &configJson)
{
if (!ensureLogosContext("initialize"))
{
return false;
}

MessageCallback actualCallback = [this](const std::string& timestamp, const std::string& nick, const std::string& message) {
// Parse JSON configuration
QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8());
if (!doc.isObject())
{
qWarning() << "ChatPlugin::initialize - Invalid JSON config:" << configJson;
return false;
}

QJsonObject config = doc.object();
int mode = config["mode"].toInt(0);
QString bootstrapNodes = config["bootstrapNodes"].toString();
QString mixnodes = config["mixnodes"].toString();
QString storeNode = config["storeNode"].toString();
QString nodeKey = config["nodeKey"].toString();

// Parse comma-separated strings and convert to std::vector<std::string>
QStringList bootstrapNodesList = bootstrapNodes.split(",", Qt::SkipEmptyParts);
QStringList mixnodesList = mixnodes.split(",", Qt::SkipEmptyParts);

std::vector<std::string> bootstrapNodesVec;
for (const QString &node : bootstrapNodesList)
{
bootstrapNodesVec.push_back(node.trimmed().toStdString());
}

std::vector<std::string> mixnodesVec;
for (const QString &mixnode : mixnodesList)
{
mixnodesVec.push_back(mixnode.trimmed().toStdString());
}

DiscoveryMode discoveryMode = static_cast<DiscoveryMode>(mode);

qDebug() << "ChatPlugin::initialize - mode:" << mode
<< ", bootstrap nodes:" << bootstrapNodesList.size()
<< ", mixnodes:" << mixnodesList.size()
<< ", storeNode:" << storeNode;

MessageCallback actualCallback = [this](const std::string &timestamp, const std::string &nick, const std::string &message)
{
QVariantList data;
data << QString::fromStdString(timestamp) << QString::fromStdString(nick) << QString::fromStdString(message);

emitEvent(QStringLiteral("chatMessage"), data);
};

void* result = ::initAndStart(logosAPI, logos, currentRelayTopic, actualCallback);
// Subscribe to network metrics events once during initialization
logos->waku_module.on("mixnodePoolSizeResponse", [this](const QVariantList &data)
{ emitEvent(QStringLiteral("mixnodePoolSizeResponse"), data); });

logos->waku_module.on("lightpushPeersCountResponse", [this](const QVariantList &data)
{ emitEvent(QStringLiteral("lightpushPeersCountResponse"), data); });

void *result = ::initAndStart(logosAPI, logos, currentRelayTopic, actualCallback, discoveryMode, bootstrapNodesVec, mixnodesVec,
storeNode.toStdString(), nodeKey.toStdString());

return (result != nullptr);
}

bool ChatPlugin::joinChannel(const QString& channelName) {
if (!ensureLogosContext("joinChannel")) {
bool ChatPlugin::joinChannel(const QString &channelName)
{
if (!ensureLogosContext("joinChannel"))
{
return false;
}
return ::joinChannel(logosAPI, logos, channelName.toStdString(), currentRelayTopic);
}

void ChatPlugin::sendMessage(const QString& channelName, const QString& username, const QString& message) {
if (!ensureLogosContext("sendMessage")) {
void ChatPlugin::sendMessage(const QString &channelName, const QString &username, const QString &message)
{
if (!ensureLogosContext("sendMessage"))
{
return;
}
std::cout << "ChatPlugin::sendMessage called with channelName: " << channelName.toStdString()
Expand All @@ -69,12 +132,15 @@ void ChatPlugin::sendMessage(const QString& channelName, const QString& username
::sendMessage(logosAPI, logos, channelName.toStdString(), username.toStdString(), message.toStdString());
}

bool ChatPlugin::retrieveHistory(const std::string& channelName) {
if (!ensureLogosContext("retrieveHistory")) {
bool ChatPlugin::retrieveHistory(const std::string &channelName)
{
if (!ensureLogosContext("retrieveHistory"))
{
return false;
}

MessageCallback actualCallback = [this](const std::string& timestamp, const std::string& nick, const std::string& message) {
MessageCallback actualCallback = [this](const std::string &timestamp, const std::string &nick, const std::string &message)
{
QVariantList data;
data << QString::fromStdString(timestamp) << QString::fromStdString(nick) << QString::fromStdString(message);

Expand All @@ -85,36 +151,64 @@ bool ChatPlugin::retrieveHistory(const std::string& channelName) {
return true;
}

bool ChatPlugin::retrieveHistory(const QString& channelName) {
bool ChatPlugin::retrieveHistory(const QString &channelName)
{
return retrieveHistory(channelName.toStdString());
}

void ChatPlugin::initLogos(LogosAPI* logosAPIInstance) {
if (logos) {
void ChatPlugin::initLogos(LogosAPI *logosAPIInstance)
{
if (logos)
{
delete logos;
logos = nullptr;
}
if (logosAPI) {
if (logosAPI)
{
delete logosAPI;
logosAPI = nullptr;
}
logosAPI = logosAPIInstance;
if (logosAPI) {
if (logosAPI)
{
logos = new LogosModules(logosAPI);
}
}

void ChatPlugin::emitEvent(const QString& eventName, const QVariantList& data) {
if (!logosAPI) {
void ChatPlugin::emitEvent(const QString &eventName, const QVariantList &data)
{
if (!logosAPI)
{
qWarning() << "ChatPlugin: LogosAPI not available, cannot emit" << eventName;
return;
}

LogosAPIClient* client = logosAPI->getClient("chat");
if (!client) {
LogosAPIClient *client = logosAPI->getClient("chat");
if (!client)
{
qWarning() << "ChatPlugin: Failed to get chat client for event" << eventName;
return;
}

client->onEventResponse(this, eventName, data);
}

bool ChatPlugin::getMixnodePoolSize()
{
if (!ensureLogosContext("getMixnodePoolSize"))
{
return false;
}

return logos->waku_module.getMixnodePoolSize();
}

bool ChatPlugin::getLightpushPeersCount()
{
if (!ensureLogosContext("getLightpushPeersCount"))
{
return false;
}

return logos->waku_module.getLightpushPeersCount();
}
6 changes: 5 additions & 1 deletion chat_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ class ChatPlugin : public QObject, public ChatInterface {
QString version() const override { return "1.0.0"; }

// ChatInterface implementation
Q_INVOKABLE bool initialize() override;
Q_INVOKABLE bool initialize(const QString& configJson) override;
Q_INVOKABLE bool joinChannel(const QString& channelName) override;
Q_INVOKABLE void sendMessage(const QString& channelName, const QString& username, const QString& message) override;
Q_INVOKABLE bool retrieveHistory(const std::string& channelName) override;
Q_INVOKABLE bool retrieveHistory(const QString& channelName);

// Network metrics
Q_INVOKABLE bool getMixnodePoolSize() override;
Q_INVOKABLE bool getLightpushPeersCount() override;

// LogosAPI initialization
Q_INVOKABLE void initLogos(LogosAPI* logosAPIInstance);

Expand Down
Loading