From 1d6b704c15ee289037447fb566e7583962496650 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 26 May 2024 12:31:12 -0500 Subject: Refactor to separate interface from implementation --- include/client/web_socket_client.hpp | 4 +- include/client/websocketpp_client.hpp | 44 ++++++++++ include/data/data.hpp | 3 - include/nostr.hpp | 153 ++++------------------------------ include/nostr_service_base.hpp | 104 +++++++++++++++++++++++ include/signer/noscrypt_signer.hpp | 55 ++++++++++++ include/signer/signer.hpp | 7 +- 7 files changed, 225 insertions(+), 145 deletions(-) create mode 100644 include/client/websocketpp_client.hpp create mode 100644 include/nostr_service_base.hpp create mode 100644 include/signer/noscrypt_signer.hpp (limited to 'include') diff --git a/include/client/web_socket_client.hpp b/include/client/web_socket_client.hpp index eca8e24..19fc949 100644 --- a/include/client/web_socket_client.hpp +++ b/include/client/web_socket_client.hpp @@ -10,14 +10,14 @@ namespace nostr { namespace client { -class IWebSocketClient; - /** * @brief An interface for a WebSocket client singleton. */ class IWebSocketClient { public: + virtual ~IWebSocketClient() = default; + /** * @brief Starts the client. * @remark This method must be called before any other client methods. diff --git a/include/client/websocketpp_client.hpp b/include/client/websocketpp_client.hpp new file mode 100644 index 0000000..becf4fa --- /dev/null +++ b/include/client/websocketpp_client.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "web_socket_client.hpp" + +namespace nostr +{ +namespace client +{ +/** + * @brief An implementation of the `IWebSocketClient` interface that uses the WebSocket++ library. + */ +class WebsocketppClient : public IWebSocketClient +{ +public: + void start() override; + + void stop() override; + + void openConnection(std::string uri) override; + + bool isConnected(std::string uri) override; + + std::tuple send(std::string message, std::string uri) override; + + std::tuple send( + std::string message, + std::string uri, + std::function messageHandler) override; + + void receive(std::string uri, std::function messageHandler) override; + + void closeConnection(std::string uri) override; + +private: + typedef websocketpp::client websocketpp_client; + typedef std::unordered_map::iterator connection_hdl_iterator; + + websocketpp_client _client; + std::unordered_map _connectionHandles; + std::mutex _propertyMutex; +}; +} // namespace client +} // namespace nostr + diff --git a/include/data/data.hpp b/include/data/data.hpp index 78c17c4..46156cd 100644 --- a/include/data/data.hpp +++ b/include/data/data.hpp @@ -13,9 +13,6 @@ namespace nostr { namespace data { -class Event; -class Filters; - /** * @brief A Nostr event. * @remark All data transmitted over the Nostr protocol is encoded in JSON blobs. This struct diff --git a/include/nostr.hpp b/include/nostr.hpp index d21a86d..c555bbe 100644 --- a/include/nostr.hpp +++ b/include/nostr.hpp @@ -21,52 +21,35 @@ namespace nostr { -class NostrService; - // TODO: Create custom exception types for the nostr namespace. -class NostrService +class INostrServiceBase { public: - NostrService( - std::shared_ptr appender, - std::shared_ptr client, - std::shared_ptr signer); - NostrService( - std::shared_ptr appender, - std::shared_ptr client, - std::shared_ptr signer, - std::vector relays); - ~NostrService(); - - std::vector defaultRelays() const; - - std::vector activeRelays() const; - - std::unordered_map> subscriptions() const; + virtual ~INostrServiceBase() = default; /** * @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. */ - std::vector openRelayConnections(); + virtual std::vector openRelayConnections() = 0; /** * @brief Opens connections to the specified Nostr relays. * @returns A list of the relay URLs to which connections were successfully opened. */ - std::vector openRelayConnections(std::vector relays); + virtual std::vector openRelayConnections(std::vector relays) = 0; /** * @brief Closes all open relay connections. */ - void closeRelayConnections(); + virtual void closeRelayConnections() = 0; /** * @brief Closes any open connections to the specified Nostr relays. */ - void closeRelayConnections(std::vector relays); + virtual void closeRelayConnections(std::vector relays) = 0; /** * @brief Publishes a Nostr event to all open relay connections. @@ -74,7 +57,8 @@ public: * to which relays the event was published successfully, and to which relays the event failed * to publish. */ - std::tuple, std::vector> publishEvent(std::shared_ptr event); + virtual std::tuple, std::vector> publishEvent( + std::shared_ptr event) = 0; /** * @brief Queries all open relay connections for events matching the given set of filters, and @@ -88,7 +72,8 @@ public: * set on the filters in the range 1-64, inclusive. If no valid limit is given, it will be * defaulted to 16. */ - std::vector> queryRelays(std::shared_ptr filters); + virtual std::vector> queryRelays( + std::shared_ptr filters) = 0; /** * @brief Queries all open relay connections for events matching the given set of filters. @@ -104,11 +89,11 @@ public: * events returned from the relay for the given filters. The service will not store the * events, and they will not be accessible via `getNewEvents`. */ - std::string queryRelays( + virtual std::string queryRelays( std::shared_ptr filters, std::function)> eventHandler, std::function eoseHandler, - std::function closeHandler); + std::function closeHandler) = 0; /** * @brief Closes the subscription with the given ID on all open relay connections. @@ -116,7 +101,8 @@ public: * to which relays the message was sent successfully, and which relays failed to receive the * message. */ - std::tuple, std::vector> closeSubscription(std::string subscriptionId); + virtual std::tuple, std::vector> closeSubscription( + std::string subscriptionId) = 0; /** * @brief Closes the subscription with the given ID on the given relay. @@ -124,119 +110,12 @@ public: * @remark If the subscription does not exist on the given relay, or if the relay is not * connected, the method will do nothing and return false. */ - bool closeSubscription(std::string subscriptionId, std::string relay); + virtual bool closeSubscription(std::string subscriptionId, std::string relay) = 0; /** * @brief Closes all open subscriptions on all open relay connections. * @returns A list of any subscription IDs that failed to close. */ - std::vector closeSubscriptions(); - - /** - * @brief Closes all open subscriptions on the given relays. - * @returns A list of any subscription IDs that failed to close. - */ - std::vector closeSubscriptions(std::vector relays); - -private: - ///< The maximum number of events the service will store for each subscription. - const int MAX_EVENTS_PER_SUBSCRIPTION = 128; - - ///< The WebSocket client used to communicate with relays. - std::shared_ptr _client; - ///< The signer used to sign Nostr events. - std::shared_ptr _signer; - - ///< A mutex to protect the instance properties. - std::mutex _propertyMutex; - ///< The default set of Nostr relays to which the service will attempt to connect. - std::vector _defaultRelays; - ///< The set of Nostr relays to which the service is currently connected. - std::vector _activeRelays; - ///< A map from subscription IDs to the relays on which each subscription is open. - std::unordered_map> _subscriptions; - - /** - * @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. - */ - std::vector getConnectedRelays(std::vector 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. - */ - std::vector getUnconnectedRelays(std::vector 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); - - /** - * @brief Generates a unique subscription ID that may be used to identify event requests. - * @returns A stringified UUID. - */ - std::string generateSubscriptionId(); - - /** - * @brief Generates a message requesting a relay to close the subscription with the given ID. - * @returns A stringified JSON object representing the close request. - */ - std::string generateCloseRequest(std::string subscriptionId); - - /** - * @brief Indicates whether the the service has an open subscription with the given ID. - * @returns True if the service has the subscription, false otherwise. - */ - bool hasSubscription(std::string subscriptionId); - - /** - * @brief Indicates whether the service has an open subscription with the given ID on the given - * relay. - * @returns True if the subscription exists on the relay, false otherwise. - */ - bool hasSubscription(std::string subscriptionId, std::string relay); - - /** - * @brief Parses EVENT messages received from the relay and invokes the given event handler. - * @param message The raw message received from the relay. - * @param eventHandler A callable object that will be invoked with the subscription ID and the - * payload of the event. - * @param eoseHandler A callable object that will be invoked with the subscription ID when the - * relay sends an EOSE message, indicating it has reached the end of stored events for the - * given query. - * @param closeHandler A callable object that will be invoked with the subscription ID and the - * message sent by the relay if the subscription is ended by the relay. - */ - void onSubscriptionMessage( - std::string message, - std::function)> eventHandler, - std::function eoseHandler, - std::function closeHandler); - - /** - * @brief Parses OK messages received from the relay and invokes the given acceptance handler. - * @remark The OK message type is sent to indicate whether the relay has accepted an event sent - * by the client. Note that this is distinct from whether the message was successfully sent to - * the relay over the WebSocket connection. - */ - void onAcceptance(std::string message, std::function acceptanceHandler); + virtual std::vector closeSubscriptions() = 0; }; } // namespace nostr diff --git a/include/nostr_service_base.hpp b/include/nostr_service_base.hpp new file mode 100644 index 0000000..6abfedc --- /dev/null +++ b/include/nostr_service_base.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "nostr.hpp" + +namespace nostr +{ +class NostrServiceBase : public INostrServiceBase +{ +public: + NostrServiceBase( + std::shared_ptr appender, + std::shared_ptr client); + + NostrServiceBase( + std::shared_ptr appender, + std::shared_ptr client, + std::vector relays); + + ~NostrServiceBase() override; + + std::vector defaultRelays() const; + + std::vector activeRelays() const; + + std::unordered_map> subscriptions() const; + + std::vector openRelayConnections() override; + + std::vector openRelayConnections(std::vector relays) override; + + void closeRelayConnections() override; + + void closeRelayConnections(std::vector relays) override; + + // TODO: Make this method return a promise. + std::tuple, std::vector> publishEvent( + std::shared_ptr event) override; + + // TODO: Make this method return a promise. + // TODO: Add a timeout to this method to prevent hanging while waiting for the relay. + std::vector> queryRelays( + std::shared_ptr filters) override; + + std::string queryRelays( + std::shared_ptr filters, + std::function)> eventHandler, + std::function eoseHandler, + std::function closeHandler) override; + + std::tuple, std::vector> closeSubscription( + std::string subscriptionId) override; + + bool closeSubscription(std::string subscriptionId, std::string relay) override; + + std::vector closeSubscriptions() override; + +private: + ///< The maximum number of events the service will store for each subscription. + const int MAX_EVENTS_PER_SUBSCRIPTION = 128; + + ///< The WebSocket client used to communicate with relays. + std::shared_ptr _client; + + ///< A mutex to protect the instance properties. + std::mutex _propertyMutex; + + ///< The default set of Nostr relays to which the service will attempt to connect. + std::vector _defaultRelays; + + ///< The set of Nostr relays to which the service is currently connected. + std::vector _activeRelays; + + ///< A map from subscription IDs to the relays on which each subscription is open. + std::unordered_map> _subscriptions; + + std::vector _getConnectedRelays(std::vector relays); + + std::vector _getUnconnectedRelays(std::vector relays); + + bool _isConnected(std::string relay); + + void _eraseActiveRelay(std::string relay); + + void _connect(std::string relay); + + void _disconnect(std::string relay); + + std::string _generateSubscriptionId(); + + std::string _generateCloseRequest(std::string subscriptionId); + + bool _hasSubscription(std::string subscriptionId); + + bool _hasSubscription(std::string subscriptionId, std::string relay); + + void _onSubscriptionMessage( + std::string message, + std::function)> eventHandler, + std::function eoseHandler, + std::function closeHandler); + + void _onAcceptance(std::string message, std::function acceptanceHandler); +}; +} // namespace nostr diff --git a/include/signer/noscrypt_signer.hpp b/include/signer/noscrypt_signer.hpp new file mode 100644 index 0000000..1476303 --- /dev/null +++ b/include/signer/noscrypt_signer.hpp @@ -0,0 +1,55 @@ +#pragma once + +extern "C" +{ +#include +} + +#include "signer.hpp" + +namespace nostr +{ +namespace signer +{ +class NoscryptSigner : public INostrConnectSigner +{ +public: + NoscryptSigner(std::shared_ptr appender); + + ~NoscryptSigner(); + + void receiveConnection(std::string connectionToken) override; + + void initiateConnection( + std::string relay, + std::string name, + std::string url, + std::string description) override; + + void sign(std::shared_ptr event) override; + +private: + std::shared_ptr noscryptContext; + + std::string localPrivateKey; + std::string localPublicKey; + + /** + * @brief Initializes the noscrypt library context into the class's `context` property. + * @returns `true` if successful, `false` otherwise. + */ + std::shared_ptr _initNoscryptContext(); + + void _logNoscryptResult(NCResult result); + + /** + * @brief Generates a private/public key pair for local use. + * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty + * strings if the function failed. + * @remarks This keypair is intended for temporary use, and should not be saved or used outside + * of this class. + */ + std::tuple _createLocalKeypair(); +}; +} // namespace signer +} // namespace nostr diff --git a/include/signer/signer.hpp b/include/signer/signer.hpp index cc4fd03..e16aa38 100644 --- a/include/signer/signer.hpp +++ b/include/signer/signer.hpp @@ -14,15 +14,14 @@ namespace nostr { namespace signer { -class ISigner; -class INostrConnectSigner; - /** * @brief An interface for Nostr event signing that implements NIP-46. */ class ISigner { public: + virtual ~ISigner() = default; + /** * @brief Signs the given Nostr event. * @param event The event to sign. @@ -34,6 +33,8 @@ public: class INostrConnectSigner : public ISigner { public: + virtual ~INostrConnectSigner() = default; + virtual void receiveConnection(std::string connectionToken) = 0; virtual void initiateConnection( -- cgit