Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nostr Client #2

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fdaf89f
Link JSON library to project
buttercat1791 Jan 13, 2024
5afbca6
Declare nostr namespace classes in header
buttercat1791 Jan 14, 2024
c2a661e
Commit in-progress work
buttercat1791 Jan 15, 2024
c56882b
Update build.sh
buttercat1791 Jan 15, 2024
f106c9e
Line endings
buttercat1791 Jan 15, 2024
0e6f70b
Declare Event struct with built-in serialization
buttercat1791 Jan 21, 2024
0d187c2
Configure uWebSockets as dependency library
buttercat1791 Jan 22, 2024
762a863
Rename nostr.h -> nostr.hpp
buttercat1791 Jan 23, 2024
5913f29
Use WebSocket++ instead of uWebSockets
buttercat1791 Jan 23, 2024
e7a1733
Remove header files in include dir
buttercat1791 Jan 25, 2024
86cb5a2
Handle relay connection in NostrUtils class
buttercat1791 Jan 26, 2024
059c32a
Remove unneeded Boost libraries from CMake
buttercat1791 Jan 26, 2024
ba272ef
Fully declare the Event struct in the header
buttercat1791 Jan 28, 2024
336876a
Define publishEvent util method
buttercat1791 Jan 28, 2024
860e4eb
Clearly define required packages and add plog
buttercat1791 Jan 28, 2024
3a25d1f
Add logging with plog
buttercat1791 Jan 28, 2024
08879bf
Set LF as end-of-line sequence
buttercat1791 Jan 28, 2024
3587803
Declare the event struct in a separate file
buttercat1791 Jan 28, 2024
7a8ddac
Handle relay lists smarter
buttercat1791 Jan 28, 2024
bc39cbe
Don't try to close connections without a handle
buttercat1791 Jan 28, 2024
932e141
Make util methods public
buttercat1791 Jan 29, 2024
38cf246
Refactor some logic into helper functions
buttercat1791 Jan 29, 2024
3256b62
Setup a fail handler for the WebSocket connection
buttercat1791 Jan 30, 2024
17d0c58
Threadify connection and disconnection functions
buttercat1791 Jan 30, 2024
b970a01
Add concurrency to publishEvent method
buttercat1791 Jan 31, 2024
6cd0f71
Fix forward-declared signature
buttercat1791 Feb 5, 2024
88b4144
Rework build scheme and fix bugs
buttercat1791 Feb 8, 2024
9fe0ff9
Use the this pointer in NostrUtils methods
buttercat1791 Feb 9, 2024
c4bcc12
Better forward-declaration for nostr::Event struct
buttercat1791 Feb 9, 2024
ee6d786
Wrap event method definitions in namespace
buttercat1791 Feb 9, 2024
b2c5ea3
Use underscore naming for private members
buttercat1791 Feb 9, 2024
705c32c
Declare public getter for _defaultRelays member
buttercat1791 Feb 9, 2024
03f580e
Configure testing for nostr-ndk
buttercat1791 Feb 10, 2024
37eb943
gitrepublic-sdk -> NostrSDK
buttercat1791 Feb 18, 2024
07085fb
Add unit tests for NostrUtils constructor
buttercat1791 Feb 18, 2024
bef9abf
Refactor service layer and WebSocket client layer
buttercat1791 Feb 20, 2024
36db5ad
Test Nostr service with IWebSocketClient mock
buttercat1791 Feb 26, 2024
832a0a8
Unit tests and other things
buttercat1791 Feb 27, 2024
ed0a9c4
Add more unit tests for connection management
buttercat1791 Feb 29, 2024
d7f33bb
Report both successes and failures on publish
buttercat1791 Feb 29, 2024
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.vscode

build
out
out
32 changes: 18 additions & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
cmake_minimum_required(VERSION 3.5)
project(GitRepublic-Core)
cmake_minimum_required(VERSION 3.10)
project(gitrepublic-core)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ../../out/bin/${CMAKE_BUILD_TYPE})
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ../../out/lib/${CMAKE_BUILD_TYPE})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../../out/bin/${CMAKE_BUILD_TYPE})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ../../out/lib/${CMAKE_BUILD_TYPE})

# ============== HELLO WORLD EXAMPLE ==============
set (SOURCE_DIR ./src)
enable_testing()
include(GoogleTest)

set (SOURCES
${SOURCE_DIR}/gitrepublic.cpp
)
set (INCLUDE_DIR ./include)
set(SOURCE_DIR ./src)
set(SOURCES ${SOURCE_DIR}/gitrepublic.cpp)

set (HEADERS
${INCLUDE_DIR}/gitrepublic.h
)
set(INCLUDE_DIR ./include)
include_directories(${INCLUDE_DIR})
set(HEADERS ${INCLUDE_DIR}/gitrepublic.hpp)

add_subdirectory(${SOURCE_DIR}/nostr)

add_executable(gitrepublic-core ${SOURCES} ${HEADERS})
add_executable(gitrepublic-core-test ${SOURCES} ${HEADERS} ${TESTS})

target_include_directories(gitrepublic-core PRIVATE ${INCLUDE_DIR})
# =================================================
target_include_directories(gitrepublic-core PUBLIC ${DEPENDENCIES_INCLUDE_DIR})

target_link_libraries(gitrepublic-core PRIVATE NostrSDK)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ The user could clear any existing build by setting the flag `-c` to `All|Release

## Installing Dependencies

TODO
TODO
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,4 @@ fi
cd "build/$BUILD_MODE"
cmake -D CMAKE_BUILD_TYPE=$BUILD_MODE ../..

make -f $MAKEFILE -j $NBTHREADS $TARGETNAMES
make -f $MAKEFILE -j $NBTHREADS $TARGETNAMES
49 changes: 49 additions & 0 deletions include/client/web_socket_client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include <string>

namespace client
{
/**
* @brief An interface for a WebSocket client singleton.
*/
class IWebSocketClient
{
public:
/**
* @brief Starts the client.
* @remark This method must be called before any other client methods.
*/
virtual void start() = 0;

/**
* @brief Stops the client.
* @remark This method should be called when the client is no longer needed, before it is
* destroyed.
*/
virtual void stop() = 0;

/**
* @brief Opens a connection to the given server.
*/
virtual void openConnection(std::string uri) = 0;

/**
* @brief Indicates whether the client is connected to the given server.
* @returns True if the client is connected, false otherwise.
*/
virtual bool isConnected(std::string uri) = 0;

/**
* @brief Sends the given message to the given server.
* @returns A tuple indicating the server URI and whether the message was successfully
* sent.
*/
virtual std::tuple<std::string, bool> send(std::string message, std::string uri) = 0;

/**
* @brief Closes the connection to the given server.
*/
virtual void closeConnection(std::string uri) = 0;
};
} // namespace client
Empty file removed include/gitrepublic.h
Empty file.
3 changes: 3 additions & 0 deletions include/gitrepublic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

#include <string>
117 changes: 117 additions & 0 deletions include/nostr/nostr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#pragma once

#include <mutex>
#include <string>
#include <tuple>
#include <vector>

#include <nlohmann/json.hpp>
#include <plog/Log.h>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include "client/web_socket_client.hpp"

namespace nostr
{
typedef std::vector<std::string> RelayList;

// TODO: Add null checking to seralization and deserialization methods.
/**
* @brief A Nostr event.
* @remark All data transmitted over the Nostr protocol is encoded in JSON blobs. This struct
* is common to every Nostr event kind. The significance of each event is determined by the
* `tags` and `content` fields.
*/
struct Event
{
std::string id; ///< SHA-256 hash of the event data.
std::string pubkey; ///< Public key of the event creator.
std::string created_at; ///< Unix timestamp of the event creation.
int kind; ///< Event kind.
std::vector<std::vector<std::string>> tags; ///< Arbitrary event metadata.
std::string content; ///< Event content.
std::string sig; ///< Event signature created with the private key of the event creator.

nlohmann::json serialize() const;
void deserialize(std::string jsonString);
};

class NostrService
{
public:
NostrService(plog::IAppender* appender, client::IWebSocketClient* client);
NostrService(plog::IAppender* appender, client::IWebSocketClient* client, RelayList relays);
~NostrService();

RelayList defaultRelays() const;

/**
* @brief Opens connections to the default Nostr relays of the instance, as specified in
* the constructor.
* @return A list of the relay URLs to which connections were successfully opened.
*/
RelayList openRelayConnections();

/**
* @brief Opens connections to the specified Nostr relays.
* @returns A list of the relay URLs to which connections were successfully opened.
*/
RelayList openRelayConnections(RelayList relays);

/**
* @brief Closes all open relay connections.
*/
void closeRelayConnections();

/**
* @brief Closes any open connections to the specified Nostr relays.
*/
void closeRelayConnections(RelayList relays);

/**
* @brief Publishes a Nostr event to all open relay connections.
* @returns A list of the relay URLs to which the event was successfully published.
*/
RelayList publishEvent(Event event);

SilberWitch marked this conversation as resolved.
Show resolved Hide resolved
private:
std::mutex _propertyMutex;
RelayList _defaultRelays;
RelayList _activeRelays;
client::IWebSocketClient* _client;

/**
* @brief Determines which of the given relays are currently connected.
* @returns A list of the URIs of currently-open relay connections from the given list.
*/
RelayList getConnectedRelays(RelayList relays);

/**
* @brief Determines which of the given relays are not currently connected.
* @returns A list of the URIs of currently-unconnected relays from the given list.
*/
RelayList getUnconnectedRelays(RelayList relays);

/**
* @brief Determines whether the given relay is currently connected.
* @returns True if the relay is connected, false otherwise.
*/
bool isConnected(std::string relay);

/**
* @brief Removes the given relay from the instance's list of active relays.
*/
void eraseActiveRelay(std::string relay);

/**
* @brief Opens a connection from the client to the given relay.
*/
void connect(std::string relay);

/**
* @brief Closes the connection from the client to the given relay.
*/
void disconnect(std::string relay);
};
} // namespace nostr
9 changes: 9 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#! /bin/bash

set -eux

# ================ CLONE QT SOURCES ================
git clone [email protected]:qt/qtbase.git

# ================ INSTALLING QT DEPENDENCIES ================
sudo apt-get install -y qt6-base-dev
101 changes: 101 additions & 0 deletions src/client/websocketpp_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include "web_socket_client.hpp"

using namespace std;

namespace client
{
/**
* @brief An implementation of the `IWebSocketClient` interface that uses the WebSocket++ library.
*/
class WebsocketppClient : public IWebSocketClient
{
public:
void start() override
{
this->_client.init_asio();
this->_client.start_perpetual();
};

void stop() override
{
this->_client.stop_perpetual();
this->_client.stop();
};

void openConnection(string uri) override
{
error_code error;
websocketpp_client::connection_ptr connection = this->_client.get_connection(uri, error);

if (error.value() == -1)
{
// PLOG_ERROR << "Error connecting to relay " << relay << ": " << error.message();
}

// Configure the connection here via the connection pointer.
connection->set_fail_handler([this, uri](auto handle) {
// PLOG_ERROR << "Error connecting to relay " << relay << ": Handshake failed.";
lock_guard<mutex> lock(this->_propertyMutex);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isConnected also locks the mutex - I assume it works, but the lock_guard could be placed inside the if

if (this->isConnected(uri))
{
this->_connectionHandles.erase(uri);
}
});

lock_guard<mutex> lock(this->_propertyMutex);
this->_connectionHandles[uri] = connection->get_handle();
this->_client.connect(connection);
};

bool isConnected(string uri) override
{
lock_guard<mutex> lock(this->_propertyMutex);
return this->_connectionHandles.find(uri) != this->_connectionHandles.end();
};

tuple<string, bool> send(string message, string uri) override
{
error_code error;

// Make sure the connection isn't closed from under us.
lock_guard<mutex> lock(this->_propertyMutex);
this->_client.send(
this->_connectionHandles[uri],
message,
websocketpp::frame::opcode::text,
error);

if (error.value() == -1)
{
// PLOG_ERROR << "Error publishing event to relay " << relay << ": " << error.message();
return make_tuple(uri, false);
}

return make_tuple(uri, true);
};

void closeConnection(string uri) override
{
lock_guard<mutex> lock(this->_propertyMutex);

websocketpp::connection_hdl handle = this->_connectionHandles[uri];
this->_client.close(
handle,
websocketpp::close::status::going_away,
"_client requested close.");

this->_connectionHandles.erase(uri);
};

private:
typedef websocketpp::client<websocketpp::config::asio_client> websocketpp_client;
typedef unordered_map<string, websocketpp::connection_hdl>::iterator connection_hdl_iterator;

websocketpp_client _client;
unordered_map<string, websocketpp::connection_hdl> _connectionHandles;
mutex _propertyMutex;
};
} // namespace client
5 changes: 3 additions & 2 deletions src/gitrepublic.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#include <gitrepublic.h>
#include <stdio.h>

#include "gitrepublic.hpp"

int main(int argc, char **argv)
{
printf("Hello GitRepublic\n");
}
}
Loading