-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Port existing work over from ShadowySupercode/gitrepublic-core#2 - Adjust project layout and configure CMake scripts accordingly
- Loading branch information
1 parent
d02431d
commit 6b99d3b
Showing
8 changed files
with
1,053 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,3 +30,9 @@ | |
*.exe | ||
*.out | ||
*.app | ||
|
||
# Outputs | ||
build/ | ||
|
||
# VS Code Settings | ||
.vscode/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.