librats 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -696,3 +696,18 @@ export function getGitDescribe(): string;
696
696
  */
697
697
  export function getAbi(): number;
698
698
 
699
+ // ============ Logging Control Functions ============
700
+
701
+ /**
702
+ * Enable or disable console logging.
703
+ * When disabled, log messages will not be printed to stdout/stderr.
704
+ * File logging (if enabled) will still work.
705
+ * @param enabled - Whether to enable console logging (default: true)
706
+ */
707
+ export function setConsoleLoggingEnabled(enabled: boolean): void;
708
+
709
+ /**
710
+ * Check if console logging is currently enabled.
711
+ * @returns true if console logging is enabled
712
+ */
713
+ export function isConsoleLoggingEnabled(): boolean;
@@ -455,7 +455,8 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
455
455
  }
456
456
 
457
457
  // ----- 2. DECRYPT IF NEEDED -----
458
- std::string data;
458
+ // Use vector directly to avoid unnecessary string conversions
459
+ std::vector<uint8_t> data;
459
460
 
460
461
  if (noise_handshake_done) {
461
462
  std::string current_peer_id;
@@ -492,16 +493,18 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
492
493
  }
493
494
 
494
495
  plaintext.resize(pt_len);
495
- data = std::string(plaintext.begin(), plaintext.end());
496
+ data = std::move(plaintext);
496
497
  LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
497
498
  } else {
498
- data = std::string(received_bytes.begin(), received_bytes.end());
499
+ data = std::move(received_bytes);
499
500
  }
500
501
  } else {
501
- data = std::string(received_bytes.begin(), received_bytes.end());
502
+ data = std::move(received_bytes);
502
503
  }
503
504
 
504
- LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << data.substr(0, 50) << (data.length() > 50 ? "..." : ""));
505
+ // Log first 50 bytes for debugging
506
+ size_t log_len = (std::min)(data.size(), static_cast<size_t>(50));
507
+ LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << std::string(data.begin(), data.begin() + log_len) << (data.size() > 50 ? "..." : ""));
505
508
 
506
509
  // ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
507
510
  if (!handshake_completed) {
@@ -531,51 +534,75 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
531
534
  }
532
535
 
533
536
  // ----- 4. HANDSHAKE PHASE -----
534
- if (is_handshake_message(data)) {
537
+ // Only check for handshake messages BEFORE handshake is completed
538
+ // This avoids expensive JSON parsing on every message after handshake
539
+ if (!handshake_completed && is_handshake_message(data)) {
535
540
  if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
536
541
  LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
537
542
  break;
538
543
  }
539
544
 
540
- // Check if handshake just completed
545
+ // Check if rats handshake just completed (COMPLETED or NOISE_PENDING state)
541
546
  if (!handshake_completed) {
542
547
  RatsPeer peer_copy;
543
- bool just_completed = false;
548
+ bool rats_handshake_done = false;
549
+ bool needs_noise_handshake = false;
544
550
 
545
551
  {
546
552
  std::lock_guard<std::mutex> lock(peers_mutex_);
547
553
  auto sock_it = socket_to_peer_id_.find(client_socket);
548
554
  if (sock_it != socket_to_peer_id_.end()) {
549
555
  auto peer_it = peers_.find(sock_it->second);
550
- if (peer_it != peers_.end() && peer_it->second.is_handshake_completed()) {
551
- just_completed = true;
552
- peer_copy = peer_it->second;
556
+ if (peer_it != peers_.end()) {
557
+ // Check if rats handshake completed (either COMPLETED or NOISE_PENDING)
558
+ if (peer_it->second.is_handshake_completed()) {
559
+ rats_handshake_done = true;
560
+ peer_copy = peer_it->second;
561
+ } else if (peer_it->second.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
562
+ rats_handshake_done = true;
563
+ needs_noise_handshake = true;
564
+ peer_copy = peer_it->second;
565
+ }
553
566
  }
554
567
  }
555
568
  }
556
569
 
557
570
  // ----- POST-HANDSHAKE ACTIONS -----
558
- if (just_completed) {
559
- handshake_completed = true;
560
- LOG_CLIENT_INFO("Handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
571
+ if (rats_handshake_done) {
572
+ LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
561
573
 
562
574
  // Remove from reconnection queue if present (successful connection)
563
575
  remove_from_reconnect_queue(peer_copy.peer_id);
564
576
 
565
577
  // Noise encryption handshake - only if BOTH sides support encryption
566
578
  // peer_copy.encryption_enabled is already negotiated in handle_handshake_message()
567
- if (peer_copy.encryption_enabled) {
579
+ if (needs_noise_handshake) {
568
580
  LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
569
581
  if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
570
582
  noise_handshake_done = true;
571
583
  LOG_CLIENT_INFO("Noise handshake successful for peer " << peer_copy.peer_id);
584
+
585
+ // Update state to COMPLETED after successful Noise handshake
586
+ {
587
+ std::lock_guard<std::mutex> lock(peers_mutex_);
588
+ auto sock_it = socket_to_peer_id_.find(client_socket);
589
+ if (sock_it != socket_to_peer_id_.end()) {
590
+ auto peer_it = peers_.find(sock_it->second);
591
+ if (peer_it != peers_.end()) {
592
+ peer_it->second.handshake_state = RatsPeer::HandshakeState::COMPLETED;
593
+ log_handshake_completion_unlocked(peer_it->second);
594
+ }
595
+ }
596
+ }
572
597
  } else {
573
598
  LOG_CLIENT_ERROR("Noise handshake failed for peer " << peer_copy.peer_id);
599
+ // Connection will be closed due to failed Noise handshake
600
+ break;
574
601
  }
575
- } else {
576
- LOG_CLIENT_DEBUG("Skipping Noise handshake for peer " << peer_copy.peer_id << " - encryption not negotiated");
577
602
  }
578
603
 
604
+ handshake_completed = true;
605
+
579
606
  // Connection callback
580
607
  if (connection_callback_) {
581
608
  connection_callback_(client_socket, peer_copy.peer_id);
@@ -621,11 +648,11 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
621
648
  continue;
622
649
  }
623
650
 
624
- std::vector<uint8_t> received_data(data.begin(), data.end());
651
+ // Use data directly - no need for extra copy
625
652
  MessageHeader header;
626
653
  std::vector<uint8_t> payload;
627
654
 
628
- if (!parse_message_with_header(received_data, header, payload)) {
655
+ if (!parse_message_with_header(data, header, payload)) {
629
656
  LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
630
657
  continue;
631
658
  }
@@ -741,14 +768,15 @@ std::string RatsClient::create_handshake_message(const std::string& message_type
741
768
  handshake_msg["message_type"] = message_type;
742
769
  handshake_msg["timestamp"] = timestamp;
743
770
  handshake_msg["encryption_enabled"] = is_encryption_enabled();
771
+ handshake_msg["listen_port"] = listen_port_;
744
772
 
745
773
  return handshake_msg.dump();
746
774
  }
747
775
 
748
- bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMessage& out_msg) const {
776
+ bool RatsClient::parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const {
749
777
  try {
750
- // Use nlohmann::json for proper JSON parsing
751
- nlohmann::json json_msg = nlohmann::json::parse(message);
778
+ // Use nlohmann::json with iterators to avoid string conversion
779
+ nlohmann::json json_msg = nlohmann::json::parse(data.begin(), data.end());
752
780
 
753
781
  // Clear the output structure
754
782
  out_msg = HandshakeMessage{};
@@ -762,6 +790,8 @@ bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMe
762
790
  out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
763
791
  // Parse encryption_enabled (default to false for backward compatibility)
764
792
  out_msg.encryption_enabled = json_msg.value("encryption_enabled", false);
793
+ // Parse listen_port (default to 0 for backward compatibility with older clients)
794
+ out_msg.listen_port = json_msg.value("listen_port", static_cast<uint16_t>(0));
765
795
 
766
796
  return true;
767
797
 
@@ -823,37 +853,30 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
823
853
  return true;
824
854
  }
825
855
 
826
- bool RatsClient::is_handshake_message(const std::string& message) const {
856
+ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
827
857
  try {
828
- std::string json_to_parse = message;
829
-
830
858
  // Check if message has our message header (starts with "RATS" magic)
831
- std::vector<uint8_t> message_data(message.begin(), message.end());
832
859
  MessageHeader header;
833
860
  std::vector<uint8_t> payload;
834
861
 
835
- if (parse_message_with_header(message_data, header, payload)) {
862
+ if (parse_message_with_header(data, header, payload)) {
836
863
  // Message has valid header - extract the JSON payload
837
864
  if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
838
- json_to_parse = std::string(payload.begin(), payload.end());
839
- } else {
840
- // Handshake messages should be string/JSON type
841
- return false;
865
+ // Parse the JSON message directly from payload
866
+ nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
867
+ std::string expected_protocol;
868
+ {
869
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
870
+ expected_protocol = custom_protocol_name_;
871
+ }
872
+ return json_msg.value("protocol", "") == expected_protocol &&
873
+ json_msg.value("message_type", "") == "handshake";
842
874
  }
843
- } else {
844
- // Message has no header
875
+ // Handshake messages should be string/JSON type
845
876
  return false;
846
877
  }
847
-
848
- // Parse the JSON message
849
- nlohmann::json json_msg = nlohmann::json::parse(json_to_parse);
850
- std::string expected_protocol;
851
- {
852
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
853
- expected_protocol = custom_protocol_name_;
854
- }
855
- return json_msg.value("protocol", "") == expected_protocol &&
856
- json_msg.value("message_type", "") == "handshake";
878
+ // Message has no header
879
+ return false;
857
880
  } catch (const std::exception&) {
858
881
  return false;
859
882
  }
@@ -898,28 +921,25 @@ bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id)
898
921
  return send_handshake_unlocked(socket, our_peer_id);
899
922
  }
900
923
 
901
- bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::string& message) {
902
- // Extract JSON payload from message header if present
903
- std::string json_to_parse = message;
904
- std::vector<uint8_t> message_data(message.begin(), message.end());
924
+ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data) {
925
+ // Extract JSON payload from message header
905
926
  MessageHeader header;
906
927
  std::vector<uint8_t> payload;
907
928
 
908
- if (parse_message_with_header(message_data, header, payload)) {
909
- // Message has valid header - extract the JSON payload
910
- if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
911
- json_to_parse = std::string(payload.begin(), payload.end());
912
- } else {
913
- LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
914
- return false;
915
- }
916
- } else {
929
+ if (!parse_message_with_header(data, header, payload)) {
917
930
  LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
918
931
  return false;
919
932
  }
920
933
 
934
+ // Message has valid header - check the type
935
+ if (header.type != MessageDataType::STRING && header.type != MessageDataType::JSON) {
936
+ LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
937
+ return false;
938
+ }
939
+
940
+ // Parse handshake message directly from payload (no string conversion)
921
941
  HandshakeMessage handshake_msg;
922
- if (!parse_handshake_message(json_to_parse, handshake_msg)) {
942
+ if (!parse_handshake_message(payload, handshake_msg)) {
923
943
  LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
924
944
  return false;
925
945
  }
@@ -995,12 +1015,35 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
995
1015
  << ", remote=" << remote_encryption
996
1016
  << ", result=" << peer.encryption_enabled);
997
1017
 
1018
+ // For incoming connections, update port to the peer's actual listen port
1019
+ // This is critical for peer exchange to work correctly
1020
+ if (!peer.is_outgoing && handshake_msg.listen_port > 0) {
1021
+ // Remove old address mapping
1022
+ address_to_peer_id_.erase(peer.normalized_address);
1023
+
1024
+ // Update port and normalized address
1025
+ peer.port = handshake_msg.listen_port;
1026
+ peer.normalized_address = normalize_peer_address(peer.ip, peer.port);
1027
+
1028
+ // Add new address mapping
1029
+ address_to_peer_id_[peer.normalized_address] = peer.peer_id;
1030
+
1031
+ LOG_CLIENT_INFO("Updated incoming peer port to listen_port: " << peer.ip << ":" << peer.port);
1032
+ }
1033
+
998
1034
  // Simplified handshake logic - just one message type
999
1035
  if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1000
1036
  // This is an incoming handshake - send our handshake back
1001
1037
  if (send_handshake_unlocked(socket, get_our_peer_id())) {
1002
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1003
- log_handshake_completion_unlocked(peer);
1038
+ // If encryption is enabled, we need to do Noise handshake first
1039
+ // Set NOISE_PENDING to prevent other threads from sending messages
1040
+ if (peer.encryption_enabled) {
1041
+ peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1042
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1043
+ } else {
1044
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1045
+ log_handshake_completion_unlocked(peer);
1046
+ }
1004
1047
 
1005
1048
  // Append to historical peers file after successful connection
1006
1049
  append_peer_to_historical_file(peer);
@@ -1013,8 +1056,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1013
1056
  }
1014
1057
  } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
1015
1058
  // This is a response to our handshake
1016
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1017
- log_handshake_completion_unlocked(peer);
1059
+ // If encryption is enabled, we need to do Noise handshake first
1060
+ // Set NOISE_PENDING to prevent other threads from sending messages
1061
+ if (peer.encryption_enabled) {
1062
+ peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1063
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1064
+ } else {
1065
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1066
+ log_handshake_completion_unlocked(peer);
1067
+ }
1018
1068
 
1019
1069
  // Append to historical peers file after successful connection
1020
1070
  append_peer_to_historical_file(peer);
@@ -2377,8 +2427,8 @@ nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
2377
2427
  payload["peer_id"] = peer.peer_id;
2378
2428
  payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2379
2429
 
2380
- // Create rats message
2381
- return create_rats_message("peer", payload, peer.peer_id);
2430
+ // Create rats message - use OUR peer_id as sender, not the advertised peer's id
2431
+ return create_rats_message("peer", payload, get_our_peer_id());
2382
2432
  }
2383
2433
 
2384
2434
  void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
@@ -48,9 +48,10 @@ struct RatsPeer {
48
48
  // Handshake-related fields
49
49
  enum class HandshakeState {
50
50
  PENDING, // Handshake not started
51
- SENT, // Handshake sent, waiting for response
52
- COMPLETED, // Handshake completed successfully
53
- FAILED // Handshake failed
51
+ SENT, // Handshake sent, waiting for response
52
+ NOISE_PENDING, // Rats handshake done, Noise handshake in progress
53
+ COMPLETED, // Handshake completed successfully (including Noise if enabled)
54
+ FAILED // Handshake failed
54
55
  };
55
56
 
56
57
  HandshakeState handshake_state; // Current handshake state
@@ -990,6 +991,20 @@ public:
990
991
  // Logging Control API
991
992
  // =========================================================================
992
993
 
994
+ /**
995
+ * Enable or disable console logging
996
+ * When disabled, log messages will not be printed to stdout/stderr
997
+ * File logging (if enabled) will still work
998
+ * @param enabled Whether to enable console logging (default: true)
999
+ */
1000
+ void set_console_logging_enabled(bool enabled);
1001
+
1002
+ /**
1003
+ * Check if console logging is currently enabled
1004
+ * @return true if console logging is enabled
1005
+ */
1006
+ bool is_console_logging_enabled() const;
1007
+
993
1008
  /**
994
1009
  * Enable or disable file logging
995
1010
  * When enabled, logs will be written to "rats.log" by default
@@ -2046,15 +2061,16 @@ private:
2046
2061
  std::string message_type;
2047
2062
  int64_t timestamp;
2048
2063
  bool encryption_enabled; // Whether peer supports/wants encryption
2064
+ uint16_t listen_port; // Peer's listening port for peer exchange
2049
2065
  };
2050
2066
 
2051
2067
  std::string create_handshake_message(const std::string& message_type, const std::string& our_peer_id) const;
2052
- bool parse_handshake_message(const std::string& message, HandshakeMessage& out_msg) const;
2068
+ bool parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const;
2053
2069
  bool validate_handshake_message(const HandshakeMessage& msg) const;
2054
- bool is_handshake_message(const std::string& message) const;
2070
+ bool is_handshake_message(const std::vector<uint8_t>& data) const;
2055
2071
  bool send_handshake(socket_t socket, const std::string& our_peer_id);
2056
2072
  bool send_handshake_unlocked(socket_t socket, const std::string& our_peer_id);
2057
- bool handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::string& message);
2073
+ bool handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data);
2058
2074
  void check_handshake_timeouts();
2059
2075
  void log_handshake_completion_unlocked(const RatsPeer& peer);
2060
2076
 
@@ -161,6 +161,14 @@ char* rats_get_connection_statistics_json(rats_client_t handle) {
161
161
  return rats_strdup_owned(json.dump());
162
162
  }
163
163
 
164
+ void rats_set_console_logging_enabled(int enabled) {
165
+ Logger::getInstance().set_console_logging_enabled(enabled != 0);
166
+ }
167
+
168
+ int rats_is_console_logging_enabled(void) {
169
+ return Logger::getInstance().is_console_logging_enabled() ? 1 : 0;
170
+ }
171
+
164
172
  void rats_set_logging_enabled(int enabled) {
165
173
  // Global logger control through any client instance is awkward; use singleton
166
174
  Logger::getInstance().set_file_logging_enabled(enabled != 0);
@@ -41,6 +41,8 @@ RATS_API char** rats_get_peer_ids(rats_client_t client, int* count); // caller m
41
41
  RATS_API char* rats_get_peer_info_json(rats_client_t client, const char* peer_id); // caller must free
42
42
 
43
43
  // Logging controls (optional helpers)
44
+ RATS_API void rats_set_console_logging_enabled(int enabled);
45
+ RATS_API int rats_is_console_logging_enabled(void);
44
46
  RATS_API void rats_set_logging_enabled(int enabled);
45
47
  RATS_API void rats_set_log_level(const char* level_str); // "DEBUG", "INFO", "WARN", "ERROR"
46
48
  RATS_API void rats_set_log_file_path(rats_client_t client, const char* file_path);
@@ -103,6 +103,11 @@ std::vector<uint8_t> RatsClient::get_peer_handshake_hash(const std::string& peer
103
103
  }
104
104
 
105
105
  bool RatsClient::send_noise_message(socket_t socket, const uint8_t* data, size_t len) {
106
+ // Get socket-specific mutex for thread-safe sending
107
+ // This prevents race conditions with other threads sending rats protocol messages
108
+ auto socket_mutex = get_socket_send_mutex(socket);
109
+ std::lock_guard<std::mutex> send_lock(*socket_mutex);
110
+
106
111
  // Send length-prefixed message for Noise handshake
107
112
  uint32_t network_len = htonl(static_cast<uint32_t>(len));
108
113
 
@@ -14,6 +14,18 @@ namespace librats {
14
14
  // Logging Control API Implementation
15
15
  //=============================================================================
16
16
 
17
+ void RatsClient::set_console_logging_enabled(bool enabled) {
18
+ Logger::getInstance().set_console_logging_enabled(enabled);
19
+ // Only log if console is enabled, otherwise we're silently disabling
20
+ if (enabled) {
21
+ LOG_CLIENT_INFO("Console logging enabled");
22
+ }
23
+ }
24
+
25
+ bool RatsClient::is_console_logging_enabled() const {
26
+ return Logger::getInstance().is_console_logging_enabled();
27
+ }
28
+
17
29
  void RatsClient::set_logging_enabled(bool enabled) {
18
30
  LOG_CLIENT_INFO("Setting file logging " << (enabled ? "enabled" : "disabled"));
19
31
 
@@ -59,6 +59,17 @@ public:
59
59
  timestamps_enabled_ = enabled;
60
60
  }
61
61
 
62
+ // Console logging configuration
63
+ void set_console_logging_enabled(bool enabled) {
64
+ std::lock_guard<std::mutex> lock(mutex_);
65
+ console_logging_enabled_ = enabled;
66
+ }
67
+
68
+ bool is_console_logging_enabled() const {
69
+ std::lock_guard<std::mutex> lock(mutex_);
70
+ return console_logging_enabled_;
71
+ }
72
+
62
73
  // File logging configuration
63
74
  void set_file_logging_enabled(bool enabled) {
64
75
  std::lock_guard<std::mutex> lock(mutex_);
@@ -147,13 +158,15 @@ public:
147
158
  // Add message
148
159
  console_oss << " " << message << std::endl;
149
160
 
150
- // Output to appropriate console stream
151
- if (level >= LogLevel::ERROR) {
152
- std::cerr << console_oss.str();
153
- std::cerr.flush();
154
- } else {
155
- std::cout << console_oss.str();
156
- std::cout.flush();
161
+ // Output to appropriate console stream (if console logging is enabled)
162
+ if (console_logging_enabled_) {
163
+ if (level >= LogLevel::ERROR) {
164
+ std::cerr << console_oss.str();
165
+ std::cerr.flush();
166
+ } else {
167
+ std::cout << console_oss.str();
168
+ std::cout.flush();
169
+ }
157
170
  }
158
171
 
159
172
  // Also write to file if file logging is enabled
@@ -164,8 +177,8 @@ public:
164
177
 
165
178
  private:
166
179
  Logger() : min_level_(LogLevel::INFO), colors_enabled_(true), timestamps_enabled_(true),
167
- file_logging_enabled_(false), max_log_file_size_(10 * 1024 * 1024),
168
- max_log_files_(5), current_file_size_(0) {
180
+ console_logging_enabled_(true), file_logging_enabled_(false),
181
+ max_log_file_size_(10 * 1024 * 1024), max_log_files_(5), current_file_size_(0) {
169
182
  // Check if we're outputting to a terminal
170
183
  is_terminal_ = isatty(fileno(stdout));
171
184
 
@@ -366,6 +379,9 @@ private:
366
379
  bool timestamps_enabled_;
367
380
  bool is_terminal_;
368
381
 
382
+ // Console logging control
383
+ bool console_logging_enabled_;
384
+
369
385
  // File logging members
370
386
  bool file_logging_enabled_;
371
387
  std::string log_file_path_;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "librats",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Node.js bindings for librats - A high-performance peer-to-peer networking library",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1330,6 +1330,25 @@ Napi::Value GetVersion(const Napi::CallbackInfo& info) {
1330
1330
  return version;
1331
1331
  }
1332
1332
 
1333
+ // Console logging control functions
1334
+ Napi::Value SetConsoleLoggingEnabled(const Napi::CallbackInfo& info) {
1335
+ Napi::Env env = info.Env();
1336
+
1337
+ if (info.Length() < 1 || !info[0].IsBoolean()) {
1338
+ Napi::TypeError::New(env, "Boolean expected").ThrowAsJavaScriptException();
1339
+ return env.Undefined();
1340
+ }
1341
+
1342
+ bool enabled = info[0].As<Napi::Boolean>().Value();
1343
+ rats_set_console_logging_enabled(enabled ? 1 : 0);
1344
+ return env.Undefined();
1345
+ }
1346
+
1347
+ Napi::Value IsConsoleLoggingEnabled(const Napi::CallbackInfo& info) {
1348
+ Napi::Env env = info.Env();
1349
+ return Napi::Boolean::New(env, rats_is_console_logging_enabled() != 0);
1350
+ }
1351
+
1333
1352
  Napi::Value GetGitDescribe(const Napi::CallbackInfo& info) {
1334
1353
  Napi::Env env = info.Env();
1335
1354
  const char* git_describe = rats_get_git_describe();
@@ -1399,6 +1418,10 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
1399
1418
  exports.Set("getGitDescribe", Napi::Function::New(env, GetGitDescribe));
1400
1419
  exports.Set("getAbi", Napi::Function::New(env, GetAbi));
1401
1420
 
1421
+ // Export logging control functions
1422
+ exports.Set("setConsoleLoggingEnabled", Napi::Function::New(env, SetConsoleLoggingEnabled));
1423
+ exports.Set("isConsoleLoggingEnabled", Napi::Function::New(env, IsConsoleLoggingEnabled));
1424
+
1402
1425
  // Export constants
1403
1426
  exports.Set("constants", InitConstants(env));
1404
1427