diff --git a/packages/streamr-libstreamrproxyclient/CMakeLists.txt b/packages/streamr-libstreamrproxyclient/CMakeLists.txt index 01add4a9..11487c20 100644 --- a/packages/streamr-libstreamrproxyclient/CMakeLists.txt +++ b/packages/streamr-libstreamrproxyclient/CMakeLists.txt @@ -66,14 +66,18 @@ endif() if(NOT TARGET streamr::streamr-utils) find_package(streamr-utils CONFIG REQUIRED) endif() +if(NOT TARGET streamr::streamr-eventemitter) + find_package(streamr-eventemitter CONFIG REQUIRED) +endif() find_package(ada CONFIG REQUIRED) target_include_directories(streamrproxyclient PUBLIC include) -target_link_libraries(streamrproxyclient +target_link_libraries(streamrproxyclient PRIVATE streamr::streamr-trackerless-network PRIVATE streamr::streamr-logger PRIVATE streamr::streamr-dht PRIVATE streamr::streamr-utils + PRIVATE streamr::streamr-eventemitter PRIVATE ada::ada PRIVATE Folly::folly ) @@ -125,6 +129,37 @@ if (NOT IOS) PRIVATE GTest::gtest_main ) + add_executable(streamr-streamrnode-test-integration test/integration/StreamrNodeTest.cpp) + target_link_directories(streamr-streamrnode-test-integration PUBLIC ${SHAREDLIB_OUTPUT_DIRECTORY}) + + target_include_directories(streamr-streamrnode-test-integration PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) + + target_link_libraries(streamr-streamrnode-test-integration + PRIVATE streamrproxyclient + PRIVATE GTest::gtest + PRIVATE GTest::gtest_main + ) + + # streamrNode* C API <-> TS network end-to-end test (milestone D): + # drives the pinned @streamr/trackerless-network npm package in a + # child node process (test/ts-interop/entryPointNode.js). Skips when + # no node runtime >= 20 is available. Like the trackerless-network + # interop test this is a manual-run target (not registered with + # ctest): it downloads npm packages on first run. + add_executable(streamr-streamrnode-test-ts-interop test/ts-interop/StreamrNodeTsInteropTest.cpp) + target_compile_definitions(streamr-streamrnode-test-ts-interop + PRIVATE TS_INTEROP_HARNESS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/test/ts-interop" + ) + target_link_directories(streamr-streamrnode-test-ts-interop PUBLIC ${SHAREDLIB_OUTPUT_DIRECTORY}) + + target_include_directories(streamr-streamrnode-test-ts-interop PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) + + target_link_libraries(streamr-streamrnode-test-ts-interop + PRIVATE streamrproxyclient + PRIVATE GTest::gtest + PRIVATE GTest::gtest_main + ) + add_executable(streamr-streamrproxyclient-test-end-to-end test/end-to-end/PublishToTsServerTest.cpp) target_link_directories(streamr-streamrproxyclient-test-end-to-end PUBLIC ${CMAKE_CURRENT_LIST_DIR}/build) @@ -150,6 +185,9 @@ if (NOT IOS) if (NOT (${VCPKG_TARGET_TRIPLET} MATCHES "android")) include(GoogleTest) gtest_discover_tests(streamr-streamrproxyclient-test-integration) + # The networked streamrNode* tests need more than the 25 s + # default on the 3-core CI runners. + gtest_discover_tests(streamr-streamrnode-test-integration PROPERTIES TIMEOUT 600) gtest_discover_tests(streamrproxyclient-cpp-wrapper-test) # DISABLED: these tests build the whole streamr-dev/network TS # monorepo at a 2024 commit (ts-integration/install.sh), which no @@ -170,8 +208,9 @@ if (NOT IOS) # WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" # ) endif() - # Install header to dist/target-triplet + # Install headers to dist/target-triplet install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/streamrproxyclient.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/streamrnode.h DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/dist/${VCPKG_TARGET_TRIPLET}/include) # Install library to dist/target-triplet diff --git a/packages/streamr-libstreamrproxyclient/include/streamrnode.h b/packages/streamr-libstreamrproxyclient/include/streamrnode.h new file mode 100644 index 00000000..45fc3a22 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/include/streamrnode.h @@ -0,0 +1,177 @@ +#ifndef STREAMR_NODE_H +#define STREAMR_NODE_H + +// Public C API of the full Streamr network node (milestone D of +// trackerless-network-completion-plan.md). +// +// This API lives in the same shared library as the proxy-client API +// (streamrproxyclient.h) and reuses its infrastructure on purpose: +// - Results are reported through the same const ProxyResult** out +// parameter in every call; every returned result (success or error) +// MUST be freed with proxyClientResultDelete(). +// - The library lifecycle functions proxyClientInitLibrary() / +// proxyClientCleanupLibrary() govern this API too; +// proxyClientCleanupLibrary() stops and deletes any nodes that are +// still alive. As with the proxy API, it MUST be called before the +// process exits (see the note in streamrproxyclient.h). +// - Entry points are (websocketUrl, ethereumAddress) pairs with the +// same layout as struct Proxy; the node id of an entry point is +// derived from its ethereum address exactly like the proxy API +// derives peer ids, so any streamrNode* node can act as an entry +// point for another one. +// NAMING: whether the shared types/lifecycle should be renamed to a +// streamr* prefix (a breaking change for proxy-API users) is left as a +// review decision for this PR. + +#include // NOLINT +#include "streamrproxyclient.h" + +#define ERROR_NODE_NOT_FOUND "NODE_NOT_FOUND" +#define ERROR_NODE_NOT_STARTED "NODE_NOT_STARTED" +#define ERROR_NODE_ALREADY_STARTED "NODE_ALREADY_STARTED" +#define ERROR_NODE_STOPPED "NODE_STOPPED" +#define ERROR_INVALID_ENTRY_POINT_URL "INVALID_ENTRY_POINT_URL" +#define ERROR_SUBSCRIPTION_NOT_FOUND "SUBSCRIPTION_NOT_FOUND" +#define ERROR_NODE_OPERATION_FAILED "NODE_OPERATION_FAILED" + +// An entry point has the same shape as a proxy: the websocket URL the +// node listens on plus the ethereum address its node id is derived +// from. +typedef Proxy StreamrEntryPoint; + +typedef struct StreamrNodeConfig { + // Entry points of the layer-0 DHT. May be NULL when numEntryPoints + // is 0; the node then joins its own DHT and acts as the first entry + // point of a new network (it should have a websocket server port in + // that case so that others can join through it). + const StreamrEntryPoint* entryPoints; + uint64_t numEntryPoints; + // Websocket server port. 0 = do not run a websocket server; the + // node then connects out as a client (websocket client + WebRTC + // connections), which is the mode intended for mobile devices. + uint16_t websocketPort; + // Host name or IP address the websocket server advertises in the + // node's peer descriptor. NULL = "127.0.0.1". Ignored when + // websocketPort is 0. + const char* websocketHost; + // Accept incoming proxy connections from proxy clients (a node with + // this enabled cannot itself use streamrNodeSetProxies; the two + // roles are mutually exclusive, as in the TypeScript + // implementation). + bool acceptProxyConnections; +} StreamrNodeConfig; + +// Direction of a proxied stream part connection; the values match +// ProxyDirection in protos/NetworkRpc.proto. +typedef enum StreamrProxyDirection { + STREAMR_PROXY_DIRECTION_PUBLISH = 0, + STREAMR_PROXY_DIRECTION_SUBSCRIBE = 1 +} StreamrProxyDirection; + +// Message callback of streamrNodeSubscribe(). Invoked on an internal +// network thread: return quickly, and do NOT call streamrNode* / +// proxyClient* functions from inside the callback. content points to a +// buffer of contentLength bytes that is only valid during the call. +// streamPartId is in canonical form (partition without leading zeros, +// e.g. "0xabc...#1" even if the subscription said "#01"). +typedef void (*StreamrNodeMessageCallback)( + uint64_t nodeHandle, + const char* streamPartId, + const char* content, + uint64_t contentLength, + void* userData); + +// Creates a network node (not yet started; no sockets are opened). +// ownEthereumAddress is the node's identity: its node id is derived +// from it, and it is used as the publisher id of published messages. +// config may be NULL, which is equivalent to a zeroed config (no entry +// points, no websocket server). Returns the node handle, or 0 on error. +EXTERN_C SHARED_EXPORT uint64_t streamrNodeNew( + const ProxyResult** result, + const char* ownEthereumAddress, + const StreamrNodeConfig* config); + +// Stops the node if it is still running and releases it. The handle is +// invalid afterwards. +EXTERN_C SHARED_EXPORT void streamrNodeDelete( + const ProxyResult** result, uint64_t nodeHandle); + +// Starts the node: opens the websocket server (if configured) and +// joins the layer-0 DHT through the configured entry points. Blocks +// until the node is connected to the network. A node cannot be started +// twice, and a stopped node cannot be restarted. +EXTERN_C SHARED_EXPORT void streamrNodeStart( + const ProxyResult** result, uint64_t nodeHandle); + +// Stops the node and leaves the network. Terminal: the node cannot be +// restarted (create a new one instead). Idempotent. +EXTERN_C SHARED_EXPORT void streamrNodeStop( + const ProxyResult** result, uint64_t nodeHandle); + +// Joins a stream part. streamPartEntryPoints optionally names nodes +// known to be in the stream part (same convention as the layer-0 entry +// points); when none are given the stream part's entry points are +// discovered through the DHT. Returns once the join has been initiated; +// use streamrNodeGetNeighborCount to observe the topology forming. +EXTERN_C SHARED_EXPORT void streamrNodeJoinStreamPart( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const StreamrEntryPoint* streamPartEntryPoints, + uint64_t numStreamPartEntryPoints); + +// Leaves a stream part. +EXTERN_C SHARED_EXPORT void streamrNodeLeaveStreamPart( + const ProxyResult** result, uint64_t nodeHandle, const char* streamPartId); + +// Publishes a message to a stream part (joining it first if needed). +// content is contentLength bytes of opaque payload. ethereumPrivateKey +// may be NULL for an unsigned message; when given, the message is +// signed with ECDSA (secp256k1, EVM style) exactly like +// proxyClientPublish. Fire-and-forget: delivery into the overlay is +// not acknowledged. +EXTERN_C SHARED_EXPORT void streamrNodePublish( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const char* content, + uint64_t contentLength, + const char* ethereumPrivateKey); + +// Subscribes to a stream part (joining it first if needed): callback is +// invoked for every message received on it. Returns a subscription +// handle for streamrNodeUnsubscribe, or 0 on error. +EXTERN_C SHARED_EXPORT uint64_t streamrNodeSubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + StreamrNodeMessageCallback callback, + void* userData); + +// Removes a subscription. The callback may still be invoked for +// messages whose dispatch already started when this returns. +EXTERN_C SHARED_EXPORT void streamrNodeUnsubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + uint64_t subscriptionHandle); + +// Number of neighbors the node currently has in a stream part's +// delivery topology. +EXTERN_C SHARED_EXPORT uint64_t streamrNodeGetNeighborCount( + const ProxyResult** result, uint64_t nodeHandle, const char* streamPartId); + +// Connects the stream part through proxy nodes instead of joining its +// topology (the proxy mode of the old proxy-client API folded into the +// node handle). direction selects publishing or subscribing through +// the proxies; connectionCount 0 means "connect to all given proxies". +// Passing numProxies 0 disconnects the stream part from its proxies. +EXTERN_C SHARED_EXPORT void streamrNodeSetProxies( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const Proxy* proxies, + uint64_t numProxies, + StreamrProxyDirection direction, + uint64_t connectionCount); + +#endif diff --git a/packages/streamr-libstreamrproxyclient/lint.sh b/packages/streamr-libstreamrproxyclient/lint.sh index e011bf16..abeb0437 100755 --- a/packages/streamr-libstreamrproxyclient/lint.sh +++ b/packages/streamr-libstreamrproxyclient/lint.sh @@ -2,7 +2,7 @@ set -e -FILES=$(find . -type d \( -name src -o -name include -o -name test \) ! -path '*/build/*' ! -path '*/android/*' ! -path '*/android-library-module/*' ! -path '*/ios/*' ! -path '*/proto/*' -print0 | xargs -0 -I{} find {} -type f \( -name "*.hpp" -o -name "*.cpp" \) -print0 | xargs -0 echo) +FILES=$(find . -type d \( -name src -o -name include -o -name test \) ! -path '*/build/*' ! -path '*/android/*' ! -path '*/android-library-module/*' ! -path '*/ios/*' ! -path '*/proto/*' ! -path '*/node_modules/*' -print0 | xargs -0 -I{} find {} -type f \( -name "*.hpp" -o -name "*.cpp" \) ! -path '*/node_modules/*' -print0 | xargs -0 echo) # Compile database: the standalone build dir when present, else the root # tree's (the Linux CI legs skip the standalone builds — MODERNIZATION.md # "After the consolidation: CI speed" — and lint against the root tree, diff --git a/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp b/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp index 11624c73..fa819a1b 100644 --- a/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp +++ b/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp @@ -1,13 +1,21 @@ -// Implementation of the public C API (streamrproxyclient.h). +// Implementation of the public C API (streamrproxyclient.h — the proxy +// client — and streamrnode.h — the full network node; the two APIs +// share the result registry and the library lifecycle, so they live in +// one translation unit). // // CONSOLIDATED (MODERNIZATION.md Phase 2.6 consolidation): the former // internal header LibProxyClientApi.hpp was merged into this file and // the sibling streamr packages are consumed as C++ modules (import) // instead of textual includes. Only third-party libraries, the standard -// library and the public C API header remain textual includes. -#include "streamrproxyclient.h" +// library and the public C API headers remain textual includes. + +// Coroutine definitions need std::coroutine_traits declared in THIS +// translation unit; it cannot arrive through an imported BMI. +#include // IWYU pragma: keep + #include #include // NOLINT +#include #include #include #include @@ -20,17 +28,24 @@ #include #include #include +#include "streamrnode.h" import streamr.trackerlessnetwork.protos; import streamr.dht.ConnectionManager; import streamr.dht.Connectivity; import streamr.dht.ConnectorFacade; +import streamr.dht.DhtNode; import streamr.dht.FakeTransport; import streamr.dht.Identifiers; import streamr.dht.protos; +import streamr.eventemitter.EventEmitter; import streamr.logger.SLogger; +import streamr.trackerlessnetwork.ContentDeliveryManager; +import streamr.trackerlessnetwork.NetworkNode; +import streamr.trackerlessnetwork.NetworkStack; import streamr.trackerlessnetwork.ProxyClient; import streamr.utils.BinaryUtils; +import streamr.utils.CoroutineHelper; import streamr.utils.EthereumAddress; import streamr.utils.SigningUtils; import streamr.utils.StreamPartID; @@ -41,6 +56,7 @@ using ::dht::ConnectivityMethod; using ::dht::ConnectivityResponse; using ::dht::NodeType; using ::dht::PeerDescriptor; +using streamr::dht::DhtNodeOptions; using streamr::dht::Identifiers; using streamr::dht::connection::ConnectionManager; using streamr::dht::connection::ConnectionManagerOptions; @@ -49,10 +65,16 @@ using streamr::dht::connection::DefaultConnectorFacadeOptions; using streamr::dht::helpers::Connectivity; using streamr::dht::transport::FakeEnvironment; using streamr::dht::transport::FakeTransport; +using streamr::eventemitter::HandlerToken; using streamr::logger::SLogger; +using streamr::trackerlessnetwork::ContentDeliveryManagerOptions; +using streamr::trackerlessnetwork::createNetworkNode; +using streamr::trackerlessnetwork::NetworkNode; +using streamr::trackerlessnetwork::NetworkOptions; using streamr::trackerlessnetwork::proxy::ProxyClient; using streamr::trackerlessnetwork::proxy::ProxyClientOptions; using streamr::utils::BinaryUtils; +using streamr::utils::blockingWait; using streamr::utils::EthereumAddress; using streamr::utils::SigningUtils; using streamr::utils::StreamPartID; @@ -546,8 +568,12 @@ class LibProxyClientApi { return successfullyConnected.size(); } - static StreamMessage createStreamMessage( - const std::shared_ptr& proxyClient, + // Shared by proxyClientPublish and streamrNodePublish: the message + // layout and the signature payload are identical for both APIs. + static StreamMessage buildStreamMessage( + std::string publisherIdHex, + const StreamPartID& streamPartID, + int32_t sequenceNumber, const char* content, uint64_t contentLength, const char* ethereumPrivateKey) { @@ -559,9 +585,6 @@ class LibProxyClientApi { contentMessage->set_encryptiontype(EncryptionType::NONE); MessageID messageId; - std::string publisherIdHex = - proxyClient->getProxyClient()->getLocalEthereumAddress(); - if (!publisherIdHex.starts_with("0x")) { publisherIdHex = "0x" + publisherIdHex; } @@ -572,9 +595,8 @@ class LibProxyClientApi { std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count()); - messageId.set_sequencenumber(proxyClient->getNextSequenceNumber()); + messageId.set_sequencenumber(sequenceNumber); - auto streamPartID = proxyClient->getProxyClient()->getStreamPartID(); messageId.set_streampartition( static_cast( StreamPartIDUtils::getStreamPartition(streamPartID).value())); @@ -623,6 +645,21 @@ class LibProxyClientApi { } return message; } + + static StreamMessage createStreamMessage( + const std::shared_ptr& proxyClient, + const char* content, + uint64_t contentLength, + const char* ethereumPrivateKey) { + return buildStreamMessage( + proxyClient->getProxyClient()->getLocalEthereumAddress(), + proxyClient->getProxyClient()->getStreamPartID(), + proxyClient->getNextSequenceNumber(), + content, + contentLength, + ethereumPrivateKey); + } + uint64_t proxyClientPublish( const ProxyResult** proxyResult, uint64_t clientHandle, @@ -681,6 +718,626 @@ class LibProxyClientApi { return 0; } } + + // --- streamrnode.h implementation (the full network node API; it + // --- shares the result registry and helpers of the proxy API) ------ + +private: + class StreamrNodeWrapper { + private: + uint64_t handle; + std::shared_ptr networkNode; + std::string ownEthereumAddress; + std::atomic sequenceNumber = 1; + std::atomic started{false}; + std::atomic stopped{false}; + std::map subscriptions; + std::mutex subscriptionsMutex; + + public: + StreamrNodeWrapper( + uint64_t handle, + std::shared_ptr networkNode, + std::string ownEthereumAddress) + : handle(handle), + networkNode(std::move(networkNode)), + ownEthereumAddress(std::move(ownEthereumAddress)) {} + + // NOLINTNEXTLINE(bugprone-exception-escape) + ~StreamrNodeWrapper() { + try { + this->stop(); + } catch (const std::exception& e) { + SLogger::error( + "Exception while stopping streamr node in destructor: " + + std::string(e.what())); + } + } + + // Idempotent; only ever stops a node that reached started state. + void stop() { + if (this->started.load() && !this->stopped.exchange(true)) { + blockingWait(this->networkNode->stop()); + } + } + + std::shared_ptr& getNetworkNode() { + return this->networkNode; + } + + [[nodiscard]] const std::string& getOwnEthereumAddress() const { + return this->ownEthereumAddress; + } + + [[nodiscard]] bool isStarted() const { return this->started.load(); } + void setStarted() { this->started.store(true); } + [[nodiscard]] bool isStopped() const { return this->stopped.load(); } + void setStopped() { this->stopped.store(true); } + + int32_t getNextSequenceNumber() { + return this->sequenceNumber.fetch_add(1); + } + + void addSubscription(uint64_t subscriptionHandle, HandlerToken token) { + std::scoped_lock lock(this->subscriptionsMutex); + this->subscriptions.emplace(subscriptionHandle, token); + } + + std::optional takeSubscription( + uint64_t subscriptionHandle) { + std::scoped_lock lock(this->subscriptionsMutex); + auto subscription = this->subscriptions.find(subscriptionHandle); + if (subscription == this->subscriptions.end()) { + return std::nullopt; + } + auto token = subscription->second; + this->subscriptions.erase(subscription); + return token; + } + }; + + std::map> streamrNodes; + std::recursive_mutex streamrNodesMutex; + + std::shared_ptr findStreamrNode( + const ProxyResult** result, uint64_t nodeHandle) { + std::scoped_lock lock(this->streamrNodesMutex); + auto node = this->streamrNodes.find(nodeHandle); + if (node == this->streamrNodes.end()) { + *result = addResult( + {ErrorCpp( + "Streamr node not found with handle " + + std::to_string(nodeHandle), + ERROR_NODE_NOT_FOUND, + std::nullopt)}, + {}); + return nullptr; + } + return node->second; + } + + bool checkStreamrNodeRunning( + const ProxyResult** result, + const std::shared_ptr& node) { + if (!node->isStarted()) { + *result = addResult( + {ErrorCpp( + "The streamr node has not been started", + ERROR_NODE_NOT_STARTED, + std::nullopt)}, + {}); + return false; + } + if (node->isStopped()) { + *result = addResult( + {ErrorCpp( + "The streamr node has been stopped", + ERROR_NODE_STOPPED, + std::nullopt)}, + {}); + return false; + } + return true; + } + + std::optional parseStreamPartIdChecked( + const ProxyResult** result, const char* streamPartId) { + try { + const auto parsed = StreamPartIDUtils::parse(streamPartId); + // Canonicalize (e.g. "#01" becomes "#1"): the delivery layer + // keys stream parts by the canonical form derived from the + // message's INTEGER partition, so a non-canonical key here + // would put the subscription and the publishes of the same + // stream part into two different (and thus empty) overlays. + return streamr::utils::toStreamPartID( + StreamPartIDUtils::getStreamID(parsed), + StreamPartIDUtils::getStreamPartition(parsed).value()); + } catch (const std::invalid_argument& e) { + *result = addResult( + {ErrorCpp( + e.what(), ERROR_INVALID_STREAM_PART_ID, std::nullopt)}, + {}); + return std::nullopt; + } + } + + // Translates (websocketUrl, ethereumAddress) pairs to peer + // descriptors; invalidUrlErrorCode distinguishes the entry-point and + // proxy flavors of the same validation failure. + std::optional> parsePeerList( + const ProxyResult** result, + const Proxy* peers, + uint64_t numPeers, + const char* invalidUrlErrorCode) { + std::vector peerDescriptors; + peerDescriptors.reserve(numPeers); + for (uint64_t i = 0; i < numPeers; i++) { + const auto& peer = peers[i]; // NOLINT + try { + peerDescriptors.push_back(createProxyPeerDescriptor( + peer.ethereumAddress, peer.websocketUrl)); + } catch (const InvalidUrlException& e) { + *result = addResult( + {ErrorCpp( + e.what(), + invalidUrlErrorCode, + ProxyCpp(peer.ethereumAddress, peer.websocketUrl))}, + {}); + return std::nullopt; + } catch (const std::runtime_error& e) { + *result = addResult( + {ErrorCpp( + e.what(), + ERROR_INVALID_ETHEREUM_ADDRESS, + ProxyCpp(peer.ethereumAddress, peer.websocketUrl))}, + {}); + return std::nullopt; + } + } + return peerDescriptors; + } + +public: + uint64_t streamrNodeNew( + const ProxyResult** result, + const char* ownEthereumAddress, + const StreamrNodeConfig* config) { + std::string parsedOwnAddress; + try { + parsedOwnAddress = + std::string{toEthereumAddress(ownEthereumAddress)}; + } catch (const std::runtime_error& e) { + SLogger::error("Error in streamrNodeNew: " + std::string(e.what())); + *result = addResult( + {ErrorCpp( + e.what(), ERROR_INVALID_ETHEREUM_ADDRESS, std::nullopt)}, + {}); + return 0; + } + const StreamrNodeConfig defaultConfig{}; + if (config == nullptr) { + config = &defaultConfig; + } + auto entryPoints = parsePeerList( + result, + config->entryPoints, + config->numEntryPoints, + ERROR_INVALID_ENTRY_POINT_URL); + if (!entryPoints.has_value()) { + return 0; + } + + auto localPeerDescriptor = createLocalPeerDescriptor(parsedOwnAddress); + if (config->websocketPort != 0) { + auto* websocket = localPeerDescriptor.mutable_websocket(); + websocket->set_host( + config->websocketHost != nullptr ? config->websocketHost + : "127.0.0.1"); + websocket->set_port(config->websocketPort); + websocket->set_tls(false); + } + if (entryPoints->empty() && config->websocketPort != 0) { + // First node of a new network: it is its own entry point and + // joins its own DHT (the connectivity self-check works + // against the node's own websocket server). A node with + // NEITHER entry points NOR a websocket server keeps an empty + // entry point list: the connectivity check then reports the + // local configuration without dialing anything, and + // streamrNodeStart joins the node's own DHT explicitly. + entryPoints->push_back(localPeerDescriptor); + } + + auto networkNode = createNetworkNode( + NetworkOptions{ + .layer0 = + DhtNodeOptions{ + .peerDescriptor = localPeerDescriptor, + .entryPoints = std::move(*entryPoints)}, + .networkNode = ContentDeliveryManagerOptions{ + .acceptProxyConnections = config->acceptProxyConnections}}); + + uint64_t handle = createRandomHandle(); + auto wrapper = std::make_shared( + handle, std::move(networkNode), parsedOwnAddress); + + std::scoped_lock lock(this->streamrNodesMutex); + this->streamrNodes[handle] = wrapper; + *result = addResult({}, {}); + return handle; + } + + void streamrNodeDelete(const ProxyResult** result, uint64_t nodeHandle) { + std::shared_ptr node; + { + // Take the wrapper out of the map but stop it OUTSIDE the + // lock (stopping blocks on network teardown). + std::scoped_lock lock(this->streamrNodesMutex); + auto nodeIterator = this->streamrNodes.find(nodeHandle); + if (nodeIterator != this->streamrNodes.end()) { + node = nodeIterator->second; + this->streamrNodes.erase(nodeIterator); + } + } + node.reset(); + *result = addResult({}, {}); + } + + void streamrNodeStart(const ProxyResult** result, uint64_t nodeHandle) { + auto node = findStreamrNode(result, nodeHandle); + if (!node) { + return; + } + if (node->isStopped()) { + *result = addResult( + {ErrorCpp( + "The streamr node has been stopped and cannot be " + "restarted", + ERROR_NODE_STOPPED, + std::nullopt)}, + {}); + return; + } + if (node->isStarted()) { + *result = addResult( + {ErrorCpp( + "The streamr node has already been started", + ERROR_NODE_ALREADY_STARTED, + std::nullopt)}, + {}); + return; + } + try { + const auto& layer0Options = + node->getNetworkNode()->getOptions().layer0; + if (layer0Options.entryPoints.empty()) { + // Isolated node (no entry points, no websocket server): + // the doJoin path would wait for network connectivity + // that never comes, so join the node's own DHT + // explicitly (the pattern of the TS interop driver). + blockingWait(node->getNetworkNode()->start(false)); + blockingWait( + node->getNetworkNode() + ->getStack() + .getControlLayerNode() + .joinDht({layer0Options.peerDescriptor.value()})); + } else { + blockingWait(node->getNetworkNode()->start(true)); + } + node->setStarted(); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeStart: " + std::string(e.what())); + // Best-effort teardown of the partially started stack, then + // mark the node unusable (start is not retryable). + try { + node->setStarted(); + node->stop(); + } catch (const std::exception& stopError) { + SLogger::error( + "Exception while stopping partially started node: " + + std::string(stopError.what())); + } + node->setStopped(); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } + + void streamrNodeStop(const ProxyResult** result, uint64_t nodeHandle) { + auto node = findStreamrNode(result, nodeHandle); + if (!node) { + return; + } + if (!node->isStarted()) { + *result = addResult( + {ErrorCpp( + "The streamr node has not been started", + ERROR_NODE_NOT_STARTED, + std::nullopt)}, + {}); + return; + } + try { + node->stop(); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeStop: " + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } + + void streamrNodeJoinStreamPart( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const StreamrEntryPoint* streamPartEntryPoints, + uint64_t numStreamPartEntryPoints) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return; + } + auto entryPoints = parsePeerList( + result, + streamPartEntryPoints, + numStreamPartEntryPoints, + ERROR_INVALID_ENTRY_POINT_URL); + if (!entryPoints.has_value()) { + return; + } + try { + if (!entryPoints->empty()) { + node->getNetworkNode()->setStreamPartEntryPoints( + *parsedStreamPartId, std::move(*entryPoints)); + } + blockingWait(node->getNetworkNode()->join(*parsedStreamPartId)); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeJoinStreamPart: " + + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } + + void streamrNodeLeaveStreamPart( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return; + } + try { + blockingWait(node->getNetworkNode()->leave(*parsedStreamPartId)); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeLeaveStreamPart: " + + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } + + void streamrNodePublish( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const char* content, + uint64_t contentLength, + const char* ethereumPrivateKey) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return; + } + try { + auto message = buildStreamMessage( + node->getOwnEthereumAddress(), + *parsedStreamPartId, + node->getNextSequenceNumber(), + content, + contentLength, + ethereumPrivateKey); + blockingWait(node->getNetworkNode()->broadcast(message)); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodePublish: " + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } + + uint64_t streamrNodeSubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + StreamrNodeMessageCallback callback, + void* userData) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return 0; + } + if (callback == nullptr) { + *result = addResult( + {ErrorCpp( + "The message callback must not be NULL", + ERROR_NODE_OPERATION_FAILED, + std::nullopt)}, + {}); + return 0; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return 0; + } + try { + blockingWait(node->getNetworkNode()->join(*parsedStreamPartId)); + const std::string streamPartIdString{*parsedStreamPartId}; + const std::string streamId{ + StreamPartIDUtils::getStreamID(*parsedStreamPartId)}; + const auto streamPartition = static_cast( + StreamPartIDUtils::getStreamPartition(*parsedStreamPartId) + .value()); + auto token = node->getNetworkNode()->addMessageListener( + // The manager emits every received message; filter to the + // subscribed stream part here. Runs on an internal + // network thread (documented in streamrnode.h). + [nodeHandle, + streamPartIdString, + streamId, + streamPartition, + callback, + userData](const StreamMessage& message) { + if (message.messageid().streamid() != streamId || + message.messageid().streampartition() != + streamPartition || + !message.has_contentmessage()) { + return; + } + const auto& messageContent = + message.contentmessage().content(); + callback( + nodeHandle, + streamPartIdString.c_str(), + messageContent.data(), + messageContent.size(), + userData); + }); + uint64_t subscriptionHandle = createRandomHandle(); + node->addSubscription(subscriptionHandle, token); + *result = addResult({}, {}); + return subscriptionHandle; + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeSubscribe: " + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + return 0; + } + } + + void streamrNodeUnsubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + uint64_t subscriptionHandle) { + auto node = findStreamrNode(result, nodeHandle); + if (!node) { + return; + } + auto token = node->takeSubscription(subscriptionHandle); + if (!token.has_value()) { + *result = addResult( + {ErrorCpp( + "Subscription not found with handle " + + std::to_string(subscriptionHandle), + ERROR_SUBSCRIPTION_NOT_FOUND, + std::nullopt)}, + {}); + return; + } + node->getNetworkNode()->removeMessageListener(*token); + *result = addResult({}, {}); + } + + uint64_t streamrNodeGetNeighborCount( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return 0; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return 0; + } + auto neighborCount = + node->getNetworkNode()->getNeighbors(*parsedStreamPartId).size(); + *result = addResult({}, {}); + return neighborCount; + } + + void streamrNodeSetProxies( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const Proxy* proxies, + uint64_t numProxies, + StreamrProxyDirection direction, + uint64_t connectionCount) { + auto node = findStreamrNode(result, nodeHandle); + if (!node || !checkStreamrNodeRunning(result, node)) { + return; + } + auto parsedStreamPartId = + parseStreamPartIdChecked(result, streamPartId); + if (!parsedStreamPartId.has_value()) { + return; + } + if (direction != STREAMR_PROXY_DIRECTION_PUBLISH && + direction != STREAMR_PROXY_DIRECTION_SUBSCRIBE) { + *result = addResult( + {ErrorCpp( + "Invalid proxy direction " + + std::to_string(static_cast(direction)), + ERROR_NODE_OPERATION_FAILED, + std::nullopt)}, + {}); + return; + } + auto proxyPeerDescriptors = + parsePeerList(result, proxies, numProxies, ERROR_INVALID_PROXY_URL); + if (!proxyPeerDescriptors.has_value()) { + return; + } + const auto proxyDirection = direction == STREAMR_PROXY_DIRECTION_PUBLISH + ? ProxyDirection::PUBLISH + : ProxyDirection::SUBSCRIBE; + try { + blockingWait(node->getNetworkNode()->setProxies( + *parsedStreamPartId, + std::move(*proxyPeerDescriptors), + proxyDirection, + toEthereumAddress(node->getOwnEthereumAddress()), + connectionCount == 0 ? std::nullopt + : std::optional{connectionCount})); + *result = addResult({}, {}); + } catch (const std::exception& e) { + SLogger::error( + "Exception in streamrNodeSetProxies: " + std::string(e.what())); + *result = addResult( + {ErrorCpp(e.what(), ERROR_NODE_OPERATION_FAILED, std::nullopt)}, + {}); + } + } }; } // namespace streamr::libstreamrproxyclient @@ -756,3 +1413,104 @@ uint64_t proxyClientPublish( return getProxyClientApi().proxyClientPublish( proxyResult, clientHandle, content, contentLength, ethereumPrivateKey); } + +// --- streamrnode.h C shims --------------------------------------------- + +uint64_t streamrNodeNew( + const ProxyResult** result, + const char* ownEthereumAddress, + const StreamrNodeConfig* config) { + initFolly(); // this can be safely called multiple times + return getProxyClientApi().streamrNodeNew( + result, ownEthereumAddress, config); +} + +void streamrNodeDelete(const ProxyResult** result, uint64_t nodeHandle) { + getProxyClientApi().streamrNodeDelete(result, nodeHandle); +} + +void streamrNodeStart(const ProxyResult** result, uint64_t nodeHandle) { + getProxyClientApi().streamrNodeStart(result, nodeHandle); +} + +void streamrNodeStop(const ProxyResult** result, uint64_t nodeHandle) { + getProxyClientApi().streamrNodeStop(result, nodeHandle); +} + +void streamrNodeJoinStreamPart( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const StreamrEntryPoint* streamPartEntryPoints, + uint64_t numStreamPartEntryPoints) { + getProxyClientApi().streamrNodeJoinStreamPart( + result, + nodeHandle, + streamPartId, + streamPartEntryPoints, + numStreamPartEntryPoints); +} + +void streamrNodeLeaveStreamPart( + const ProxyResult** result, uint64_t nodeHandle, const char* streamPartId) { + getProxyClientApi().streamrNodeLeaveStreamPart( + result, nodeHandle, streamPartId); +} + +void streamrNodePublish( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const char* content, + uint64_t contentLength, + const char* ethereumPrivateKey) { + getProxyClientApi().streamrNodePublish( + result, + nodeHandle, + streamPartId, + content, + contentLength, + ethereumPrivateKey); +} + +uint64_t streamrNodeSubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + StreamrNodeMessageCallback callback, + void* userData) { + return getProxyClientApi().streamrNodeSubscribe( + result, nodeHandle, streamPartId, callback, userData); +} + +void streamrNodeUnsubscribe( + const ProxyResult** result, + uint64_t nodeHandle, + uint64_t subscriptionHandle) { + getProxyClientApi().streamrNodeUnsubscribe( + result, nodeHandle, subscriptionHandle); +} + +uint64_t streamrNodeGetNeighborCount( + const ProxyResult** result, uint64_t nodeHandle, const char* streamPartId) { + return getProxyClientApi().streamrNodeGetNeighborCount( + result, nodeHandle, streamPartId); +} + +void streamrNodeSetProxies( + const ProxyResult** result, + uint64_t nodeHandle, + const char* streamPartId, + const Proxy* proxies, + uint64_t numProxies, + StreamrProxyDirection direction, + uint64_t connectionCount) { + getProxyClientApi().streamrNodeSetProxies( + result, + nodeHandle, + streamPartId, + proxies, + numProxies, + direction, + connectionCount); +} diff --git a/packages/streamr-libstreamrproxyclient/test/integration/StreamrNodeTest.cpp b/packages/streamr-libstreamrproxyclient/test/integration/StreamrNodeTest.cpp new file mode 100644 index 00000000..9d9da564 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/integration/StreamrNodeTest.cpp @@ -0,0 +1,489 @@ +// Tests of the streamrNode* C API (streamrnode.h): input validation and +// lifecycle errors, a two-node publish/subscribe exchange over real +// websockets on 127.0.0.1, and the proxy mode folded into the node +// handle (a client-only node proxy-publishing into a full node that +// accepts proxy connections). +#include "streamrnode.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +// The C API is synchronous but topology formation is not; poll until +// the condition holds or the deadline passes. +constexpr auto pollInterval = std::chrono::milliseconds(200); + +bool waitUntil( + const std::function& condition, std::chrono::seconds timeout) { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + if (condition()) { + return true; + } + std::this_thread::sleep_for(pollInterval); + } + return condition(); +} + +// Collects the contents delivered to a subscription callback. The +// callback runs on an internal network thread, hence the mutex. +struct ReceivedMessages { + std::mutex mutex; + std::vector contents; + + static void callback( + uint64_t /* nodeHandle */, + const char* /* streamPartId */, + const char* content, + uint64_t contentLength, + void* userData) { + auto* self = static_cast(userData); + const std::scoped_lock lock(self->mutex); + self->contents.emplace_back(content, contentLength); + } + + bool contains(const std::string& expected) { + const std::scoped_lock lock(this->mutex); + return std::ranges::any_of( + this->contents, + [&expected](const auto& content) { return content == expected; }); + } +}; + +} // namespace + +class StreamrNodeTest : public ::testing::Test { +protected: + static constexpr const char* invalidEthereumAddress = + "INVALID_ETHEREUM_ADDRESS"; + static constexpr const char* ethereumAddressA = + "0x1234567890123456789012345678901234567890"; + static constexpr const char* ethereumAddressB = + "0x1234567890123456789012345678901234567892"; + // Any valid secp256k1 scalar works as a signing key; the receivers + // do not verify signatures. + static constexpr const char* ethereumPrivateKey = + "1111111111111111111111111111111111111111111111111111111111111111"; + static constexpr const char* invalidStreamPartId = "INVALID_STREAM_PART_ID"; + static constexpr const char* validStreamPartId = + "0xa000000000000000000000000000000000000000#01"; + static constexpr const char* invalidUrl = "poiejrg039utg240"; + static constexpr uint64_t nonExistentNodeHandle = 12345; + static constexpr uint16_t exchangeEntryPointPort = 44451; + static constexpr uint16_t proxyServerPort = 44452; + static constexpr auto topologyTimeout = std::chrono::seconds(60); + static constexpr auto messageTimeout = std::chrono::seconds(30); + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + static void expectSingleError( + const ProxyResult* result, const char* expectedCode) { + ASSERT_NE(result, nullptr); + ASSERT_EQ(result->numErrors, 1); + EXPECT_STREQ(result->errors[0].code, expectedCode); + } + + static void expectNoErrors(const ProxyResult* result) { + ASSERT_NE(result, nullptr); + EXPECT_EQ(result->numErrors, 0); + } + +public: + // The fixture destructor of the sibling proxy-client test cleans the + // library up after every test; re-initialize here so consecutive + // tests also work when the binary is run as a single process (under + // ctest each test is its own process and this is a no-op). + StreamrNodeTest() { proxyClientInitLibrary(); } + ~StreamrNodeTest() override { proxyClientCleanupLibrary(); } +}; + +TEST_F(StreamrNodeTest, InvalidEthereumAddress) { + const ProxyResult* result = nullptr; + uint64_t nodeHandle = + streamrNodeNew(&result, invalidEthereumAddress, nullptr); + EXPECT_EQ(nodeHandle, 0); + expectSingleError(result, ERROR_INVALID_ETHEREUM_ADDRESS); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, InvalidEntryPointUrl) { + const ProxyResult* result = nullptr; + StreamrEntryPoint entryPoint{ + .websocketUrl = invalidUrl, .ethereumAddress = ethereumAddressB}; + StreamrNodeConfig config{.entryPoints = &entryPoint, .numEntryPoints = 1}; + uint64_t nodeHandle = streamrNodeNew(&result, ethereumAddressA, &config); + EXPECT_EQ(nodeHandle, 0); + expectSingleError(result, ERROR_INVALID_ENTRY_POINT_URL); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, InvalidEntryPointEthereumAddress) { + const ProxyResult* result = nullptr; + StreamrEntryPoint entryPoint{ + .websocketUrl = "ws://127.0.0.1:40000", + .ethereumAddress = invalidEthereumAddress}; + StreamrNodeConfig config{.entryPoints = &entryPoint, .numEntryPoints = 1}; + uint64_t nodeHandle = streamrNodeNew(&result, ethereumAddressA, &config); + EXPECT_EQ(nodeHandle, 0); + expectSingleError(result, ERROR_INVALID_ETHEREUM_ADDRESS); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, NodeNotFound) { + const ProxyResult* result = nullptr; + + streamrNodeStart(&result, nonExistentNodeHandle); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeStop(&result, nonExistentNodeHandle); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeJoinStreamPart( + &result, nonExistentNodeHandle, validStreamPartId, nullptr, 0); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeLeaveStreamPart( + &result, nonExistentNodeHandle, validStreamPartId); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodePublish( + &result, nonExistentNodeHandle, validStreamPartId, "x", 1, nullptr); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + uint64_t subscriptionHandle = streamrNodeSubscribe( + &result, + nonExistentNodeHandle, + validStreamPartId, + ReceivedMessages::callback, + nullptr); + EXPECT_EQ(subscriptionHandle, 0); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeUnsubscribe(&result, nonExistentNodeHandle, 1); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeGetNeighborCount( + &result, nonExistentNodeHandle, validStreamPartId); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); + + Proxy proxy{ + .websocketUrl = "ws://127.0.0.1:40000", + .ethereumAddress = ethereumAddressB}; + streamrNodeSetProxies( + &result, + nonExistentNodeHandle, + validStreamPartId, + &proxy, + 1, + STREAMR_PROXY_DIRECTION_PUBLISH, + 1); + expectSingleError(result, ERROR_NODE_NOT_FOUND); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, CanCreateAndDeleteWithoutStarting) { + const ProxyResult* result = nullptr; + uint64_t nodeHandle = streamrNodeNew(&result, ethereumAddressA, nullptr); + EXPECT_NE(nodeHandle, 0); + expectNoErrors(result); + proxyClientResultDelete(result); + + const ProxyResult* result2 = nullptr; + streamrNodeDelete(&result2, nodeHandle); + expectNoErrors(result2); + proxyClientResultDelete(result2); +} + +TEST_F(StreamrNodeTest, OperationsRequireStart) { + const ProxyResult* result = nullptr; + uint64_t nodeHandle = streamrNodeNew(&result, ethereumAddressA, nullptr); + ASSERT_NE(nodeHandle, 0); + proxyClientResultDelete(result); + + streamrNodeJoinStreamPart( + &result, nodeHandle, validStreamPartId, nullptr, 0); + expectSingleError(result, ERROR_NODE_NOT_STARTED); + proxyClientResultDelete(result); + + streamrNodePublish(&result, nodeHandle, validStreamPartId, "x", 1, nullptr); + expectSingleError(result, ERROR_NODE_NOT_STARTED); + proxyClientResultDelete(result); + + uint64_t subscriptionHandle = streamrNodeSubscribe( + &result, + nodeHandle, + validStreamPartId, + ReceivedMessages::callback, + nullptr); + EXPECT_EQ(subscriptionHandle, 0); + expectSingleError(result, ERROR_NODE_NOT_STARTED); + proxyClientResultDelete(result); + + streamrNodeGetNeighborCount(&result, nodeHandle, validStreamPartId); + expectSingleError(result, ERROR_NODE_NOT_STARTED); + proxyClientResultDelete(result); + + streamrNodeStop(&result, nodeHandle); + expectSingleError(result, ERROR_NODE_NOT_STARTED); + proxyClientResultDelete(result); + + streamrNodeUnsubscribe(&result, nodeHandle, 1); + expectSingleError(result, ERROR_SUBSCRIPTION_NOT_FOUND); + proxyClientResultDelete(result); + + streamrNodeDelete(&result, nodeHandle); + expectNoErrors(result); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, StartStopLifecycle) { + // No entry points and no websocket server: the node starts an + // isolated network of its own (it is its own entry point). + const ProxyResult* result = nullptr; + uint64_t nodeHandle = streamrNodeNew(&result, ethereumAddressA, nullptr); + ASSERT_NE(nodeHandle, 0); + proxyClientResultDelete(result); + + streamrNodeStart(&result, nodeHandle); + expectNoErrors(result); + proxyClientResultDelete(result); + + streamrNodeStart(&result, nodeHandle); + expectSingleError(result, ERROR_NODE_ALREADY_STARTED); + proxyClientResultDelete(result); + + // The stream-part-id validation needs a running node because the + // running-state check comes first. + streamrNodeJoinStreamPart( + &result, nodeHandle, invalidStreamPartId, nullptr, 0); + expectSingleError(result, ERROR_INVALID_STREAM_PART_ID); + proxyClientResultDelete(result); + + streamrNodeStop(&result, nodeHandle); + expectNoErrors(result); + proxyClientResultDelete(result); + + // Stopping is idempotent. + streamrNodeStop(&result, nodeHandle); + expectNoErrors(result); + proxyClientResultDelete(result); + + // A stopped node cannot be restarted or used. + streamrNodeStart(&result, nodeHandle); + expectSingleError(result, ERROR_NODE_STOPPED); + proxyClientResultDelete(result); + + streamrNodePublish(&result, nodeHandle, validStreamPartId, "x", 1, nullptr); + expectSingleError(result, ERROR_NODE_STOPPED); + proxyClientResultDelete(result); + + streamrNodeDelete(&result, nodeHandle); + expectNoErrors(result); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, TwoNodesExchangeMessages) { + // Node A runs a websocket server and acts as the entry point of a + // new network; node B joins through it as a websocket client. + const ProxyResult* result = nullptr; + StreamrNodeConfig configA{.websocketPort = exchangeEntryPointPort}; + uint64_t nodeA = streamrNodeNew(&result, ethereumAddressA, &configA); + ASSERT_NE(nodeA, 0); + proxyClientResultDelete(result); + streamrNodeStart(&result, nodeA); + expectNoErrors(result); + proxyClientResultDelete(result); + + const std::string entryPointUrl = + "ws://127.0.0.1:" + std::to_string(exchangeEntryPointPort); + StreamrEntryPoint entryPoint{ + .websocketUrl = entryPointUrl.c_str(), + .ethereumAddress = ethereumAddressA}; + StreamrNodeConfig configB{.entryPoints = &entryPoint, .numEntryPoints = 1}; + uint64_t nodeB = streamrNodeNew(&result, ethereumAddressB, &configB); + ASSERT_NE(nodeB, 0); + proxyClientResultDelete(result); + streamrNodeStart(&result, nodeB); + expectNoErrors(result); + proxyClientResultDelete(result); + + // Both nodes subscribe (which also joins the stream part). + ReceivedMessages receivedA; + ReceivedMessages receivedB; + uint64_t subscriptionA = streamrNodeSubscribe( + &result, + nodeA, + validStreamPartId, + ReceivedMessages::callback, + &receivedA); + ASSERT_NE(subscriptionA, 0); + proxyClientResultDelete(result); + uint64_t subscriptionB = streamrNodeSubscribe( + &result, + nodeB, + validStreamPartId, + ReceivedMessages::callback, + &receivedB); + ASSERT_NE(subscriptionB, 0); + proxyClientResultDelete(result); + + // Wait for the stream part topology to form between the two nodes. + EXPECT_TRUE(waitUntil( + [&]() { + const ProxyResult* pollResult = nullptr; + auto neighborsA = streamrNodeGetNeighborCount( + &pollResult, nodeA, validStreamPartId); + proxyClientResultDelete(pollResult); + auto neighborsB = streamrNodeGetNeighborCount( + &pollResult, nodeB, validStreamPartId); + proxyClientResultDelete(pollResult); + return neighborsA >= 1 && neighborsB >= 1; + }, + topologyTimeout)); + + // A signed message from A to B, an unsigned one from B to A. + const std::string messageFromA = "hello from A"; + streamrNodePublish( + &result, + nodeA, + validStreamPartId, + messageFromA.data(), + messageFromA.size(), + ethereumPrivateKey); + expectNoErrors(result); + proxyClientResultDelete(result); + EXPECT_TRUE(waitUntil( + [&]() { return receivedB.contains(messageFromA); }, messageTimeout)); + + const std::string messageFromB = "hello from B"; + streamrNodePublish( + &result, + nodeB, + validStreamPartId, + messageFromB.data(), + messageFromB.size(), + nullptr); + expectNoErrors(result); + proxyClientResultDelete(result); + EXPECT_TRUE(waitUntil( + [&]() { return receivedA.contains(messageFromB); }, messageTimeout)); + + streamrNodeUnsubscribe(&result, nodeB, subscriptionB); + expectNoErrors(result); + proxyClientResultDelete(result); + streamrNodeUnsubscribe(&result, nodeB, subscriptionB); + expectSingleError(result, ERROR_SUBSCRIPTION_NOT_FOUND); + proxyClientResultDelete(result); + streamrNodeUnsubscribe(&result, nodeA, subscriptionA); + expectNoErrors(result); + proxyClientResultDelete(result); + + streamrNodeStop(&result, nodeB); + expectNoErrors(result); + proxyClientResultDelete(result); + streamrNodeStop(&result, nodeA); + expectNoErrors(result); + proxyClientResultDelete(result); + streamrNodeDelete(&result, nodeB); + proxyClientResultDelete(result); + streamrNodeDelete(&result, nodeA); + proxyClientResultDelete(result); +} + +TEST_F(StreamrNodeTest, ProxyModePublishesToAcceptingNode) { + // Node A is a full node accepting proxy connections; node B is a + // client-only node (own isolated DHT, no websocket server) that + // proxy-publishes into A — the old proxy-client use case through + // the node handle. + const ProxyResult* result = nullptr; + StreamrNodeConfig configA{ + .websocketPort = proxyServerPort, .acceptProxyConnections = true}; + uint64_t nodeA = streamrNodeNew(&result, ethereumAddressA, &configA); + ASSERT_NE(nodeA, 0); + proxyClientResultDelete(result); + streamrNodeStart(&result, nodeA); + expectNoErrors(result); + proxyClientResultDelete(result); + + ReceivedMessages receivedA; + uint64_t subscriptionA = streamrNodeSubscribe( + &result, + nodeA, + validStreamPartId, + ReceivedMessages::callback, + &receivedA); + ASSERT_NE(subscriptionA, 0); + proxyClientResultDelete(result); + + // A node that accepts proxy connections cannot itself use proxies. + const std::string proxyUrl = + "ws://127.0.0.1:" + std::to_string(proxyServerPort); + Proxy proxy{ + .websocketUrl = proxyUrl.c_str(), .ethereumAddress = ethereumAddressA}; + streamrNodeSetProxies( + &result, + nodeA, + validStreamPartId, + &proxy, + 1, + STREAMR_PROXY_DIRECTION_PUBLISH, + 1); + expectSingleError(result, ERROR_NODE_OPERATION_FAILED); + proxyClientResultDelete(result); + + uint64_t nodeB = streamrNodeNew(&result, ethereumAddressB, nullptr); + ASSERT_NE(nodeB, 0); + proxyClientResultDelete(result); + streamrNodeStart(&result, nodeB); + expectNoErrors(result); + proxyClientResultDelete(result); + + streamrNodeSetProxies( + &result, + nodeB, + validStreamPartId, + &proxy, + 1, + STREAMR_PROXY_DIRECTION_PUBLISH, + 1); + expectNoErrors(result); + proxyClientResultDelete(result); + + const std::string messageViaProxy = "hello via proxy"; + EXPECT_TRUE(waitUntil( + [&]() { + const ProxyResult* publishResult = nullptr; + streamrNodePublish( + &publishResult, + nodeB, + validStreamPartId, + messageViaProxy.data(), + messageViaProxy.size(), + nullptr); + proxyClientResultDelete(publishResult); + return receivedA.contains(messageViaProxy); + }, + messageTimeout)); + + streamrNodeStop(&result, nodeB); + proxyClientResultDelete(result); + streamrNodeStop(&result, nodeA); + proxyClientResultDelete(result); + streamrNodeDelete(&result, nodeB); + proxyClientResultDelete(result); + streamrNodeDelete(&result, nodeA); + proxyClientResultDelete(result); +} diff --git a/packages/streamr-libstreamrproxyclient/test/ts-interop/.gitignore b/packages/streamr-libstreamrproxyclient/test/ts-interop/.gitignore new file mode 100644 index 00000000..a034b860 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/ts-interop/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +npm-install.log +driver-stderr.log diff --git a/packages/streamr-libstreamrproxyclient/test/ts-interop/StreamrNodeTsInteropTest.cpp b/packages/streamr-libstreamrproxyclient/test/ts-interop/StreamrNodeTsInteropTest.cpp new file mode 100644 index 00000000..b10ef8bf --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/ts-interop/StreamrNodeTsInteropTest.cpp @@ -0,0 +1,435 @@ +// End-to-end test of the streamrNode* C API against a TypeScript +// streamr network (milestone D exit criterion for the shared-library +// surface): the TS side is the pinned @streamr/trackerless-network +// 103.8.0-rc.3 from npm running in a child node process +// (entryPointNode.js in this directory). It acts as the websocket entry +// point and stream-part entry point with a node id derived from a known +// ethereum address, echoes RECEIVED for every message it sees, +// and broadcasts on a PUBLISH stdin command. A client-only C API +// node (no websocket server — the mobile configuration) joins through +// it; a C publish must reach the TS node and a TS publish must reach +// the C callback. +// +// The pinned package's dependencies require a node runtime >= 20. +// Discovery order: $STREAMR_TS_INTEROP_NODE, `node` on PATH, newest +// ~/.nvm version. Only a missing runtime SKIPs the test; every other +// failure (npm ci, driver crash, join failure) is a hard failure. +// +// Unlike the other interop tests this one deliberately uses ONLY the +// public C API (streamrnode.h) — no C++ modules. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "streamrnode.h" + +namespace { + +constexpr uint16_t tsEntryPointPort = 17801; +constexpr const char* tsEthereumAddress = + "0x2222222222222222222222222222222222222222"; +constexpr const char* cppEthereumAddress = + "0x1111111111111111111111111111111111111111"; +constexpr const char* interopStreamPartId = "interop-stream#0"; +constexpr int minNodeMajorVersion = 20; +constexpr size_t driverStdoutChunkSize = 4096; +constexpr size_t versionLineBufferSize = 256; +// Conventional shell exit codes: command found but not executable / not +// found; used by the forked child when exec fails. +constexpr int chdirFailedExitCode = 126; +constexpr int execFailedExitCode = 127; +constexpr std::chrono::seconds driverStartTimeout{90}; +constexpr std::chrono::seconds joinTimeout{60}; +constexpr std::chrono::seconds messageTimeout{30}; + +std::optional runAndCaptureFirstLine(const std::string& command) { + FILE* pipe = popen(command.c_str(), "r"); // NOLINT + if (pipe == nullptr) { + return std::nullopt; + } + std::array buffer{}; + std::string output; + if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) { + output = buffer.data(); + } + const int status = pclose(pipe); + if (status != 0 || output.empty()) { + return std::nullopt; + } + while (!output.empty() && + (output.back() == '\n' || output.back() == '\r')) { + output.pop_back(); + } + return output; +} + +// Parses the major version out of ` --version` output ("v22.17.1"). +std::optional nodeMajorVersion(const std::string& nodeBinary) { + const auto version = + runAndCaptureFirstLine("'" + nodeBinary + "' --version 2>/dev/null"); + if (!version || version->size() < 2 || (*version)[0] != 'v') { + return std::nullopt; + } + return std::atoi(version->c_str() + 1); // NOLINT +} + +std::optional findNodeBinary() { + if (const char* fromEnv = std::getenv("STREAMR_TS_INTEROP_NODE")) { + return std::string{fromEnv}; + } + if (const auto onPath = runAndCaptureFirstLine("command -v node")) { + const auto major = nodeMajorVersion(*onPath); + if (major && *major >= minNodeMajorVersion) { + return onPath; + } + } + // Local-development convenience: PATH may point at an old node while + // nvm has suitable ones installed. + std::optional best; + int bestMajor = 0; + if (const char* home = std::getenv("HOME")) { + const auto nvmDir = + std::filesystem::path(home) / ".nvm" / "versions" / "node"; + std::error_code ec; + for (const auto& entry : + std::filesystem::directory_iterator(nvmDir, ec)) { + const auto candidate = entry.path() / "bin" / "node"; + if (!std::filesystem::exists(candidate)) { + continue; + } + const auto major = nodeMajorVersion(candidate.string()); + if (major && *major >= minNodeMajorVersion && *major > bestMajor) { + bestMajor = *major; + best = candidate.string(); + } + } + } + return best; +} + +// Collects the contents delivered to the C API subscription callback +// (invoked on an internal network thread). +struct ReceivedMessages { + std::mutex mutex; + std::set contents; + + static void callback( + uint64_t /* nodeHandle */, + const char* /* streamPartId */, + const char* content, + uint64_t contentLength, + void* userData) { + auto* self = static_cast(userData); + const std::scoped_lock lock(self->mutex); + self->contents.emplace(content, contentLength); + } + + bool contains(const std::string& expected) { + const std::scoped_lock lock(this->mutex); + return this->contents.contains(expected); + } +}; + +} // namespace + +class StreamrNodeTsInteropTest : public ::testing::Test { +protected: + pid_t driverPid = -1; + int driverStdout = -1; + int driverStdin = -1; + std::string lineBuffer; + std::mutex receivedMutex; + std::set tsReceived; // texts the TS node reported + + void SetUp() override { + proxyClientInitLibrary(); + const auto nodeBinary = findNodeBinary(); + if (!nodeBinary) { + GTEST_SKIP() << "No node runtime >= " << minNodeMajorVersion + << " found (set STREAMR_TS_INTEROP_NODE to one)"; + } + installHarnessDependencies(*nodeBinary); + if (::testing::Test::HasFatalFailure()) { + return; + } + spawnDriver(*nodeBinary); + ASSERT_NE(this->driverPid, -1); + const auto ready = readLineWithPrefix("READY", driverStartTimeout); + ASSERT_TRUE(ready.has_value()) << "TS driver did not report READY; see " + << harnessDir() / "driver-stderr.log"; + } + + void TearDown() override { + stopDriver(); + proxyClientCleanupLibrary(); + } + + static std::filesystem::path harnessDir() { + return std::filesystem::path{TS_INTEROP_HARNESS_DIR}; + } + + // npm ci into the harness directory when node_modules is absent. npm + // lives next to the chosen node binary, which is not necessarily the + // PATH one. + static void installHarnessDependencies(const std::string& nodeBinary) { + if (std::filesystem::exists(harnessDir() / "node_modules")) { + return; + } + const auto nodeBinDir = + std::filesystem::path(nodeBinary).parent_path().string(); + std::string command = "cd '" + harnessDir().string() + "' && "; + if (!nodeBinDir.empty()) { + command += "PATH='" + nodeBinDir + "':\"$PATH\" "; + } + command += "npm ci --no-audit --no-fund > npm-install.log 2>&1"; + ASSERT_EQ(std::system(command.c_str()), 0) // NOLINT + << "npm ci failed; see " << harnessDir() / "npm-install.log"; + } + + void spawnDriver(const std::string& nodeBinary) { + std::array stdoutPipe{}; + std::array stdinPipe{}; + ASSERT_EQ(pipe(stdoutPipe.data()), 0); + ASSERT_EQ(pipe(stdinPipe.data()), 0); + const pid_t pid = fork(); + ASSERT_NE(pid, -1); + if (pid == 0) { + dup2(stdoutPipe[1], STDOUT_FILENO); + dup2(stdinPipe[0], STDIN_FILENO); + close(stdoutPipe[0]); + close(stdoutPipe[1]); + close(stdinPipe[0]); + close(stdinPipe[1]); + const auto stderrPath = + (harnessDir() / "driver-stderr.log").string(); + const int stderrFd = open( + stderrPath.c_str(), + O_WRONLY | O_CREAT | O_TRUNC, + 0644); // NOLINT + if (stderrFd != -1) { + dup2(stderrFd, STDERR_FILENO); + close(stderrFd); + } + if (chdir(harnessDir().c_str()) != 0) { + _exit(chdirFailedExitCode); + } + const auto port = std::to_string(tsEntryPointPort); + execl( // NOLINT + nodeBinary.c_str(), + nodeBinary.c_str(), + "entryPointNode.js", + port.c_str(), + tsEthereumAddress, + static_cast(nullptr)); + _exit(execFailedExitCode); + } + close(stdoutPipe[1]); + close(stdinPipe[0]); + this->driverPid = pid; + this->driverStdout = stdoutPipe[0]; + this->driverStdin = stdinPipe[1]; + fcntl(this->driverStdout, F_SETFL, O_NONBLOCK); // NOLINT + } + + void stopDriver() { + if (this->driverPid != -1) { + kill(this->driverPid, SIGTERM); + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(10); // NOLINT + int status = 0; + while (waitpid(this->driverPid, &status, WNOHANG) == 0) { + if (std::chrono::steady_clock::now() > deadline) { + kill(this->driverPid, SIGKILL); + waitpid(this->driverPid, &status, 0); + break; + } + usleep(50000); // NOLINT + } + this->driverPid = -1; + } + if (this->driverStdout != -1) { + close(this->driverStdout); + this->driverStdout = -1; + } + if (this->driverStdin != -1) { + close(this->driverStdin); + this->driverStdin = -1; + } + } + + void sendDriverCommand(const std::string& line) const { + const auto data = line + "\n"; + ASSERT_EQ( + write(this->driverStdin, data.data(), data.size()), + static_cast(data.size())); + } + + // Reads driver stdout until a line starting with the given prefix + // arrives; returns the rest of that line. RECEIVED lines are + // collected into tsReceived as a side effect so no report is lost + // while waiting for something else; other lines are dropped. + std::optional readLineWithPrefix( + const std::string& prefix, std::chrono::milliseconds timeout) { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (true) { + const auto newline = this->lineBuffer.find('\n'); + if (newline != std::string::npos) { + std::string line = this->lineBuffer.substr(0, newline); + this->lineBuffer.erase(0, newline + 1); + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + if (line.starts_with("RECEIVED ")) { + const std::scoped_lock lock(this->receivedMutex); + this->tsReceived.insert( + line.substr(std::string{"RECEIVED "}.size())); + } + if (line.starts_with(prefix)) { + return line.substr(prefix.size()); + } + continue; + } + const auto now = std::chrono::steady_clock::now(); + if (now >= deadline) { + return std::nullopt; + } + struct pollfd pfd{ + .fd = this->driverStdout, .events = POLLIN, .revents = 0}; + const auto remaining = + std::chrono::duration_cast( + deadline - now); + const int pollTimeout = static_cast( + std::min(remaining.count(), 200)); // NOLINT + if (poll(&pfd, 1, pollTimeout) > 0 && + ((pfd.revents & POLLIN) != 0)) { + std::array buffer{}; + const auto n = + read(this->driverStdout, buffer.data(), buffer.size()); + if (n > 0) { + this->lineBuffer.append(buffer.data(), n); + } + } + } + } + + // Polls driver stdout until the TS node has reported receiving the + // given text (or the timeout passes). + bool waitForTsReceived( + const std::string& text, std::chrono::milliseconds timeout) { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + { + const std::scoped_lock lock(this->receivedMutex); + if (this->tsReceived.contains(text)) { + return true; + } + } + // Drain whatever the driver has printed; the prefix never + // matches, so this just pumps RECEIVED lines for up to 200ms. + readLineWithPrefix( + "\x01never", std::chrono::milliseconds(200)); // NOLINT + } + const std::scoped_lock lock(this->receivedMutex); + return this->tsReceived.contains(text); + } +}; + +TEST_F(StreamrNodeTsInteropTest, MessagesFlowBothWaysWithTsNetwork) { + const std::string entryPointUrl = + "ws://127.0.0.1:" + std::to_string(tsEntryPointPort); + StreamrEntryPoint entryPoint{ + .websocketUrl = entryPointUrl.c_str(), + .ethereumAddress = tsEthereumAddress}; + + // A client-only node: no websocket server, exactly the mobile + // configuration milestone D targets. + const ProxyResult* result = nullptr; + StreamrNodeConfig config{.entryPoints = &entryPoint, .numEntryPoints = 1}; + const uint64_t nodeHandle = + streamrNodeNew(&result, cppEthereumAddress, &config); + ASSERT_NE(nodeHandle, 0); + proxyClientResultDelete(result); + + streamrNodeStart(&result, nodeHandle); + ASSERT_EQ(result->numErrors, 0) + << "start failed: " << result->errors[0].message; + proxyClientResultDelete(result); + + streamrNodeJoinStreamPart( + &result, nodeHandle, interopStreamPartId, &entryPoint, 1); + ASSERT_EQ(result->numErrors, 0) + << "join failed: " << result->errors[0].message; + proxyClientResultDelete(result); + + ReceivedMessages received; + const uint64_t subscriptionHandle = streamrNodeSubscribe( + &result, + nodeHandle, + interopStreamPartId, + ReceivedMessages::callback, + &received); + ASSERT_NE(subscriptionHandle, 0); + proxyClientResultDelete(result); + + // Wait until the C node and the TS node are stream-part neighbors. + const auto joinDeadline = std::chrono::steady_clock::now() + joinTimeout; + uint64_t neighborCount = 0; + while (std::chrono::steady_clock::now() < joinDeadline) { + const ProxyResult* pollResult = nullptr; + neighborCount = streamrNodeGetNeighborCount( + &pollResult, nodeHandle, interopStreamPartId); + proxyClientResultDelete(pollResult); + if (neighborCount >= 1) { + break; + } + usleep(200000); // NOLINT + } + ASSERT_GE(neighborCount, 1) + << "the C node never got the TS node as a stream-part neighbor"; + + // C -> TS. + const std::string cppText = R"({ "from": "cpp-c-api" })"; + streamrNodePublish( + &result, + nodeHandle, + interopStreamPartId, + cppText.data(), + cppText.size(), + nullptr); + ASSERT_EQ(result->numErrors, 0) + << "publish failed: " << result->errors[0].message; + proxyClientResultDelete(result); + EXPECT_TRUE(this->waitForTsReceived(cppText, messageTimeout)) + << "the TS node never reported the C API message"; + + // TS -> C. + const std::string tsText = R"({ "from": "ts" })"; + sendDriverCommand("PUBLISH " + tsText); + const auto messageDeadline = + std::chrono::steady_clock::now() + messageTimeout; + while (!received.contains(tsText) && + std::chrono::steady_clock::now() < messageDeadline) { + usleep(200000); // NOLINT + } + EXPECT_TRUE(received.contains(tsText)) + << "the C API callback never got the TS message"; + + streamrNodeUnsubscribe(&result, nodeHandle, subscriptionHandle); + proxyClientResultDelete(result); + streamrNodeStop(&result, nodeHandle); + EXPECT_EQ(result->numErrors, 0); + proxyClientResultDelete(result); + streamrNodeDelete(&result, nodeHandle); + proxyClientResultDelete(result); +} diff --git a/packages/streamr-libstreamrproxyclient/test/ts-interop/entryPointNode.js b/packages/streamr-libstreamrproxyclient/test/ts-interop/entryPointNode.js new file mode 100644 index 00000000..4053b4a9 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/ts-interop/entryPointNode.js @@ -0,0 +1,109 @@ +// TS side of the streamrNode* C API end-to-end test +// (StreamrNodeTsInteropTest.cpp). +// +// Starts a TypeScript full node (NetworkStack from the pinned +// @streamr/trackerless-network npm package) as a websocket entry point +// on 127.0.0.1:, joins its own layer-0 DHT and the interop stream +// part, and then speaks a line protocol: +// +// stdout: +// READY once the node is joined and listening +// RECEIVED for every newMessage, with the utf8 content +// SENT after a PUBLISH command's broadcast was issued +// stdin: +// PUBLISH broadcast a StreamMessage with the utf8 content +// +// Unlike the trackerless-network interop driver (fullNode.js) this one +// takes the node's ethereum address as an argument and derives its DHT +// node id from it (the nodeId option of the pinned DhtNode): the C API +// reconstructs entry point descriptors from (websocketUrl, +// ethereumAddress) pairs, so the driver's real node id must match that +// derivation. region is passed explicitly because without it start() +// falls back to a GeoIP/CDN lookup over the real network. +const crypto = require('crypto') +const readline = require('readline') +const { NetworkStack } = require('@streamr/trackerless-network') +// The published package does not export the generated-protos subpath; +// the numeric enum values from protos/NetworkRpc.proto are used +// directly (ContentType.JSON = 0, EncryptionType.NONE = 0, +// SignatureType.ECDSA_SECP256K1_EVM = 1). +const CONTENT_TYPE_JSON = 0 +const ENCRYPTION_TYPE_NONE = 0 +const SIGNATURE_TYPE_ECDSA_SECP256K1_EVM = 1 +const { StreamPartIDUtils, hexToBinary, utf8ToBinary, binaryToUtf8 } = + require('@streamr/utils') + +const port = parseInt(process.argv[2], 10) +const ownEthereumAddress = process.argv[3] +if (!(port > 0) || !/^0x[0-9a-fA-F]{40}$/.test(ownEthereumAddress ?? '')) { + console.error( + 'usage: node entryPointNode.js ') + process.exit(1) +} + +const streamPartId = StreamPartIDUtils.parse('interop-stream#0') + +const createMessage = (text) => { + return { + messageId: { + streamId: StreamPartIDUtils.getStreamID(streamPartId), + streamPartition: StreamPartIDUtils.getStreamPartition(streamPartId), + timestamp: Date.now(), + sequenceNumber: 0, + publisherId: crypto.randomBytes(20), + messageChainId: 'ts-interop-chain' + }, + previousMessageRef: undefined, + body: { + oneofKind: 'contentMessage', + contentMessage: { + content: utf8ToBinary(text), + contentType: CONTENT_TYPE_JSON, + encryptionType: ENCRYPTION_TYPE_NONE + } + }, + signatureType: SIGNATURE_TYPE_ECDSA_SECP256K1_EVM, + signature: hexToBinary('0x1234') + } +} + +const main = async () => { + const stack = new NetworkStack({ + layer0: { + nodeId: ownEthereumAddress.slice(2).toLowerCase(), + websocketHost: '127.0.0.1', + websocketPortRange: { min: port, max: port }, + websocketServerEnableTls: false, + region: 0 + } + }) + // No entry points configured: join our own DHT explicitly (the + // doJoin path would wait for connectivity that never comes). + await stack.start(false) + const ownDescriptor = stack.getControlLayerNode().getLocalPeerDescriptor() + await stack.getControlLayerNode().joinDht([ownDescriptor]) + stack.getContentDeliveryManager().setStreamPartEntryPoints( + streamPartId, [ownDescriptor]) + stack.getContentDeliveryManager().joinStreamPart(streamPartId) + stack.getContentDeliveryManager().on('newMessage', (msg) => { + if (msg.body.oneofKind === 'contentMessage') { + console.log(`RECEIVED ${binaryToUtf8(msg.body.contentMessage.content)}`) + } + }) + + console.log('READY') + + const rl = readline.createInterface({ input: process.stdin }) + rl.on('line', (line) => { + if (line.startsWith('PUBLISH ')) { + const text = line.substring('PUBLISH '.length) + stack.getContentDeliveryManager().broadcast(createMessage(text)) + console.log(`SENT ${text}`) + } + }) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/packages/streamr-libstreamrproxyclient/test/ts-interop/package-lock.json b/packages/streamr-libstreamrproxyclient/test/ts-interop/package-lock.json new file mode 100644 index 00000000..e6c532a0 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/ts-interop/package-lock.json @@ -0,0 +1,2172 @@ +{ + "name": "streamr-libstreamrproxyclient-ts-interop-harness", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "streamr-libstreamrproxyclient-ts-interop-harness", + "dependencies": { + "@streamr/dht": "103.8.0-rc.3", + "@streamr/trackerless-network": "103.8.0-rc.3", + "@streamr/utils": "103.8.0-rc.3" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/post-quantum": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.4.1.tgz", + "integrity": "sha512-TRXjvnY9jAFNWbxOx+pKt21BNsCEWKFjMbIKwdx9CQXBudDanpY20EfOcooV7DIsRS/+Mf8D8utpUPjfGrQ8fA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/post-quantum/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@protobuf-ts/runtime-rpc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", + "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", + "license": "Apache-2.0", + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + } + }, + "node_modules/@streamr/autocertifier-client": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/autocertifier-client/-/autocertifier-client-103.8.0-rc.3.tgz", + "integrity": "sha512-AzVViVJ3tfgF/45hj6p/1kbS+pRniZaAvooJQNZoYT2Cpmg0AS2Ak/IN+aDu8vouW2z89voLv8I59k4wpqFLuQ==", + "license": "STREAMR NETWORK OPEN SOURCE LICENSE", + "dependencies": { + "@protobuf-ts/runtime-rpc": "^2.8.2", + "@streamr/utils": "103.8.0-rc.3", + "eventemitter3": "^5.0.0", + "node-forge": "^1.3.2" + } + }, + "node_modules/@streamr/cdn-location": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/cdn-location/-/cdn-location-103.8.0-rc.3.tgz", + "integrity": "sha512-j9KDMrDPH+SfDuGd7cScb9nIlq/xlHAV+Cr0ruJEo8BOkQm/ZgIsCN2uFVsLmuxOhF17WrRnp/tSl/YfrlmnOw==", + "license": "Apache-2.0", + "dependencies": { + "@streamr/utils": "103.8.0-rc.3", + "haversine": "^1.1.1" + } + }, + "node_modules/@streamr/dht": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/dht/-/dht-103.8.0-rc.3.tgz", + "integrity": "sha512-jb91JMV5secjAHSyxs7INNJWf8eQ8WHBUuMNqufKQi6hzZPJCiU9ZFTHbTZFm9IMbYv+qgY+Lrqu0s+5Fa6PHA==", + "license": "STREAMR NETWORK OPEN SOURCE LICENSE", + "dependencies": { + "@js-sdsl/ordered-map": "^4.4.2", + "@protobuf-ts/runtime": "^2.8.2", + "@protobuf-ts/runtime-rpc": "^2.8.2", + "@streamr/autocertifier-client": "103.8.0-rc.3", + "@streamr/cdn-location": "103.8.0-rc.3", + "@streamr/geoip-location": "103.8.0-rc.3", + "@streamr/proto-rpc": "103.8.0-rc.3", + "@streamr/utils": "103.8.0-rc.3", + "comlink": "^4.4.2", + "eventemitter3": "^5.0.0", + "heap": "^0.2.6", + "ipaddr.js": "^2.0.1", + "k-bucket": "^5.1.0", + "lodash": "^4.17.21", + "lru-cache": "^11.2.2", + "node-datachannel": "^0.27.0", + "timers-browserify": "^2.0.12", + "uuid": "^11.1.0", + "websocket": "^1.0.34", + "ws": "^8.18.3" + }, + "optionalDependencies": { + "bufferutil": "^4.0.9", + "utf-8-validate": "^6.0.5" + } + }, + "node_modules/@streamr/geoip-location": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/geoip-location/-/geoip-location-103.8.0-rc.3.tgz", + "integrity": "sha512-VA9SVMXfV+2ivnSS4rgyztd6G/NGEzvGNvvPrKbvOr97LhtlP7ApOfLK5aZDqhXgIKy33+v7vF8ms55jgZXlHw==", + "license": "Apache-2.0", + "dependencies": { + "@streamr/utils": "103.8.0-rc.3", + "eventemitter3": "^5.0.0", + "long-timeout": "^0.1.1", + "mmdb-lib": "^3.0.1", + "tar": "^7.5.2", + "uuid": "^11.1.0" + } + }, + "node_modules/@streamr/proto-rpc": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/proto-rpc/-/proto-rpc-103.8.0-rc.3.tgz", + "integrity": "sha512-b+9E2fs/Fsd4rJ6i8U/xs+Ie+XSSFS9zS2DEOt/JnubECrHrrspVi99UKkkwZB4i8vC3w4iCUZvyhkqs7C7nfw==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "dependencies": { + "@protobuf-ts/runtime": "^2.8.2", + "@protobuf-ts/runtime-rpc": "^2.8.2", + "@streamr/utils": "103.8.0-rc.3", + "eventemitter3": "^5.0.0", + "lodash": "^4.17.21", + "uuid": "^11.1.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.9", + "utf-8-validate": "^6.0.5" + } + }, + "node_modules/@streamr/trackerless-network": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/trackerless-network/-/trackerless-network-103.8.0-rc.3.tgz", + "integrity": "sha512-v6ZEADGjYAvkKkB+JAqxytchdD4WuBke3O0GCV8yY909Nu9c7JAAv+K+6i8ueUxk7dRH6ca+N8gto73M+vgFRA==", + "license": "STREAMR NETWORK OPEN SOURCE LICENSE", + "dependencies": { + "@protobuf-ts/runtime": "^2.8.2", + "@protobuf-ts/runtime-rpc": "^2.8.2", + "@streamr/dht": "103.8.0-rc.3", + "@streamr/proto-rpc": "103.8.0-rc.3", + "@streamr/utils": "103.8.0-rc.3", + "eventemitter3": "^5.0.0", + "lodash": "^4.17.21", + "ts-essentials": "^10.1.1", + "uuid": "^11.1.0", + "yallist": "^5.0.0" + } + }, + "node_modules/@streamr/utils": { + "version": "103.8.0-rc.3", + "resolved": "https://registry.npmjs.org/@streamr/utils/-/utils-103.8.0-rc.3.tgz", + "integrity": "sha512-4hDwDjzbxXjrXJ8YP70H0PYN6Y3/+zpYS+H0vJa021ZnZVAVE3P9uf1dpEskI9D3BWU8G0psPFAvGeRmZQfaaQ==", + "license": "Apache-2.0", + "dependencies": { + "@noble/curves": "^1.9.7", + "@noble/hashes": "^2.0.1", + "@noble/post-quantum": "^0.4.1", + "asn1.js": "^5.4.1", + "browserify-aes": "^1.2.0", + "buffer": "^6.0.3", + "buffer-shim": "^1.0.1", + "eventemitter3": "^5.0.0", + "hash-base": "^3.1.2", + "lodash": "^4.17.21", + "path-browserify": "^1.0.1", + "pino": "^10.1.0", + "pino-pretty": "^13.1.2", + "public-encrypt": "^4.0.3", + "readable-stream": "^4.7.0", + "secp256k1": "^5.0.1", + "sha3": "^2.1.4" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-rsa/node_modules/bn.js": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.5.tgz", + "integrity": "sha512-Vq886eXykuP5E6HcKSSStP3bJgrE6In5WKxVUvJ8XGpWWYs2xZHWqUwzCtGgEtBcxyd57KBFDPFoUfNzdaHCNg==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-shim/-/buffer-shim-1.0.1.tgz", + "integrity": "sha512-VG1oTE6Ecr9h/Gx3XOXue0F1vQaQlkBd3tGAzpL7Fsu+8f1Jbtk5ORPkrVLA/ADBTQ08bcPt3HanaZ7tUfJqMg==", + "license": "ISC", + "peerDependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fast-copy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.4.tgz", + "integrity": "sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==", + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/hash-base/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash-base/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/haversine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/haversine/-/haversine-1.1.1.tgz", + "integrity": "sha512-KW4MS8+krLIeiw8bF5z532CptG0ZyGGFj0UbKMxx25lKnnJ1hMUbuzQl+PXQjNiDLnl1bOyz23U6hSK10r4guw==", + "license": "MIT" + }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "license": "MIT" + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/k-bucket": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/k-bucket/-/k-bucket-5.1.0.tgz", + "integrity": "sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mmdb-lib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-3.0.2.tgz", + "integrity": "sha512-7e87vk0DdWT647wjcfEtWeMtjm+zVGqNohN/aeIymbUfjHQ2T4Sx5kM+1irVDBSloNC3CkGKxswdMoo8yhqTDg==", + "license": "MIT", + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-datachannel": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/node-datachannel/-/node-datachannel-0.27.0.tgz", + "integrity": "sha512-RuClwcfRYJn0YqosgkAdocEM78jo7F3z9hSZzUiogB7QegD28mFOOjMoX/CAbU4ckHaLAoppfROwZXMro/EmNA==", + "hasInstallScript": true, + "license": "MPL 2.0", + "dependencies": { + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-asn1/node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/pbkdf2": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.6.tgz", + "integrity": "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^4.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.1.tgz", + "integrity": "sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sha3": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", + "license": "MIT", + "dependencies": { + "buffer": "6.0.3" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tar": { + "version": "7.5.20", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz", + "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ts-essentials": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.2.1.tgz", + "integrity": "sha512-+Id1fRkuir+CsgK2x04/icS2b4V1hQmq7ObzIrDjhN0ozfRYivnP7aaKMVJfLApQm0trjR39A6NIMVchiB9Erw==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/websocket": { + "version": "1.0.35", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "license": "Apache-2.0", + "dependencies": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/websocket/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/packages/streamr-libstreamrproxyclient/test/ts-interop/package.json b/packages/streamr-libstreamrproxyclient/test/ts-interop/package.json new file mode 100644 index 00000000..671987a2 --- /dev/null +++ b/packages/streamr-libstreamrproxyclient/test/ts-interop/package.json @@ -0,0 +1,10 @@ +{ + "name": "streamr-libstreamrproxyclient-ts-interop-harness", + "private": true, + "description": "TS side of the streamrNode* C API end-to-end test: the pinned @streamr/trackerless-network from npm, driven by entryPointNode.js", + "dependencies": { + "@streamr/dht": "103.8.0-rc.3", + "@streamr/trackerless-network": "103.8.0-rc.3", + "@streamr/utils": "103.8.0-rc.3" + } +}