Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
- Port existing work over from ShadowySupercode/gitrepublic-core#2
- Adjust project layout and configure CMake scripts accordingly
  • Loading branch information
buttercat1791 committed Mar 3, 2024
1 parent d02431d commit 6b99d3b
Show file tree
Hide file tree
Showing 8 changed files with 1,053 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@
*.exe
*.out
*.app

# Outputs
build/

# VS Code Settings
.vscode/
78 changes: 78 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
cmake_minimum_required(VERSION 3.14)
project(NostrSDK VERSION 0.0.1)

# Specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# Build the project.
set(INCLUDE_DIR ./include)
set(CLIENT_INCLUDE_DIR ./include/client)
include_directories(${INCLUDE_DIR})
include_directories(${CLIENT_INCLUDE_DIR})
set(HEADERS
${INCLUDE_DIR}/nostr.hpp
${CLIENT_INCLUDE_DIR}/web_socket_client.hpp
)

set(SOURCE_DIR ./src)
set(CLIENT_SOURCE_DIR ./src/client)
set(SOURCES
${SOURCE_DIR}/event.cpp
${SOURCE_DIR}/nostr_service.cpp
${CLIENT_SOURCE_DIR}/websocketpp_client.cpp
)

find_package(Boost REQUIRED COMPONENTS random system)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(plog CONFIG REQUIRED)
find_package(websocketpp CONFIG REQUIRED)

add_library(NostrSDK ${SOURCES} ${HEADERS})
target_link_libraries(NostrSDK PRIVATE
Boost::random
Boost::system
nlohmann_json::nlohmann_json
OpenSSL::SSL
OpenSSL::Crypto
plog::plog
websocketpp::websocketpp
)
set_target_properties(NostrSDK PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES)

# Build the tests.
enable_testing()
include(GoogleTest)

include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)

set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

set(TEST_DIR ./test)
set(TEST_SOURCES
${TEST_DIR}/nostr_service_test.cpp
)

add_executable(NostrSDKTest ${TEST_SOURCES} ${HEADERS})
target_link_libraries(NostrSDKTest PRIVATE
GTest::gmock
GTest::gtest
GTest::gtest_main
NostrSDK
plog::plog
websocketpp::websocketpp
)
set_target_properties(NostrSDKTest PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS YES)

gtest_add_tests(TARGET NostrSDKTest)
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
123 changes: 123 additions & 0 deletions include/nostr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#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;

RelayList activeRelays() 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 tuple of `RelayList` objects, of the form `<successes, failures>`, indicating
* to which relays the event was published successfully, and to which relays the event failed
* to publish.
*/
std::tuple<RelayList, RelayList> publishEvent(Event event);

// TODO: Add methods for reading events from relays.

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
107 changes: 107 additions & 0 deletions src/client/websocketpp_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include "web_socket_client.hpp"

using std::error_code;
using std::lock_guard;
using std::make_tuple;
using std::mutex;
using std::string;
using std::tuple;
using std::unordered_map;

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);
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
Loading

0 comments on commit 6b99d3b

Please sign in to comment.