librats 1.0.2 → 2.2.0

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.
Files changed (319) hide show
  1. package/README.md +145 -331
  2. package/binding.gyp +16 -3
  3. package/lib/index.d.ts +288 -696
  4. package/lib/index.js +407 -44
  5. package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
  7. package/native-src/CMakeLists.txt +404 -179
  8. package/native-src/LICENSE +1 -1
  9. package/native-src/src/librats/bindings/rats.cpp +762 -0
  10. package/native-src/src/librats/bindings/rats.h +380 -0
  11. package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
  12. package/native-src/src/librats/bittorrent/bencode.h +176 -0
  13. package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
  14. package/native-src/src/librats/bittorrent/bitfield.h +76 -0
  15. package/native-src/src/librats/bittorrent/byte_io.h +58 -0
  16. package/native-src/src/librats/bittorrent/choker.cpp +25 -0
  17. package/native-src/src/librats/bittorrent/choker.h +46 -0
  18. package/native-src/src/librats/bittorrent/client.cpp +413 -0
  19. package/native-src/src/librats/bittorrent/client.h +227 -0
  20. package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
  21. package/native-src/src/librats/bittorrent/disk_io.h +150 -0
  22. package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
  23. package/native-src/src/librats/bittorrent/extensions.h +94 -0
  24. package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
  25. package/native-src/src/librats/bittorrent/file_storage.h +79 -0
  26. package/native-src/src/librats/bittorrent/log.h +42 -0
  27. package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
  28. package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
  29. package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
  30. package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
  31. package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
  32. package/native-src/src/librats/bittorrent/peer_list.h +75 -0
  33. package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
  34. package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
  35. package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
  36. package/native-src/src/librats/bittorrent/reactor.h +89 -0
  37. package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
  38. package/native-src/src/librats/bittorrent/resume_data.h +41 -0
  39. package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
  40. package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
  41. package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
  42. package/native-src/src/librats/bittorrent/torrent.h +260 -0
  43. package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
  44. package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
  45. package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
  46. package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
  47. package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
  48. package/native-src/src/librats/bittorrent/tracker.h +108 -0
  49. package/native-src/src/librats/bittorrent/types.cpp +206 -0
  50. package/native-src/src/librats/bittorrent/types.h +86 -0
  51. package/native-src/src/librats/core/address.cpp +35 -0
  52. package/native-src/src/librats/core/address.h +78 -0
  53. package/native-src/src/librats/core/bytes.h +69 -0
  54. package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
  55. package/native-src/src/librats/core/chained_send_buffer.h +183 -0
  56. package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
  57. package/native-src/src/librats/core/endpoint_parse.h +31 -0
  58. package/native-src/src/librats/core/event_bus.h +70 -0
  59. package/native-src/src/librats/core/host_endpoint.h +56 -0
  60. package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
  61. package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
  62. package/native-src/src/librats/core/ip_address.cpp +120 -0
  63. package/native-src/src/librats/core/ip_address.h +109 -0
  64. package/native-src/src/librats/core/mpsc_queue.h +47 -0
  65. package/native-src/src/librats/core/notifier.h +74 -0
  66. package/native-src/src/librats/core/receive_buffer.cpp +219 -0
  67. package/native-src/src/librats/core/receive_buffer.h +171 -0
  68. package/native-src/src/librats/core/service_registry.h +58 -0
  69. package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
  70. package/native-src/src/librats/core/socket.h +496 -0
  71. package/native-src/src/librats/core/timer_queue.h +105 -0
  72. package/native-src/src/librats/core/types.cpp +43 -0
  73. package/native-src/src/librats/core/types.h +103 -0
  74. package/native-src/src/librats/core/wakeup_pipe.h +83 -0
  75. package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
  76. package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
  77. package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
  78. package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
  79. package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
  80. package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
  81. package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
  82. package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
  83. package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
  84. package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
  85. package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
  86. package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
  87. package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
  88. package/native-src/src/librats/crypto/hkdf.c +266 -0
  89. package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
  90. package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
  91. package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
  92. package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
  93. package/native-src/src/librats/crypto/poly1305.h +37 -0
  94. package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
  95. package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
  96. package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
  97. package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
  98. package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
  99. package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
  100. package/native-src/src/librats/dht/announce.cpp +37 -0
  101. package/native-src/src/librats/dht/announce.h +41 -0
  102. package/native-src/src/librats/dht/bep42.cpp +109 -0
  103. package/native-src/src/librats/dht/bep42.h +48 -0
  104. package/native-src/src/librats/dht/dht.cpp +501 -0
  105. package/native-src/src/librats/dht/dht.h +119 -0
  106. package/native-src/src/librats/dht/dht_runner.cpp +103 -0
  107. package/native-src/src/librats/dht/dht_runner.h +71 -0
  108. package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
  109. package/native-src/src/librats/dht/dos_blocker.h +47 -0
  110. package/native-src/src/librats/dht/find_peers.cpp +52 -0
  111. package/native-src/src/librats/dht/find_peers.h +73 -0
  112. package/native-src/src/librats/dht/id.h +167 -0
  113. package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
  114. package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
  115. package/native-src/src/librats/dht/log.h +38 -0
  116. package/native-src/src/librats/dht/node.cpp +473 -0
  117. package/native-src/src/librats/dht/node.h +164 -0
  118. package/native-src/src/librats/dht/node_entry.h +81 -0
  119. package/native-src/src/librats/dht/observer.h +72 -0
  120. package/native-src/src/librats/dht/persistence.cpp +90 -0
  121. package/native-src/src/librats/dht/persistence.h +32 -0
  122. package/native-src/src/librats/dht/routing_table.cpp +559 -0
  123. package/native-src/src/librats/dht/routing_table.h +185 -0
  124. package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
  125. package/native-src/src/librats/dht/rpc_manager.h +77 -0
  126. package/native-src/src/librats/dht/storage.cpp +92 -0
  127. package/native-src/src/librats/dht/storage.h +74 -0
  128. package/native-src/src/librats/dht/transport.h +27 -0
  129. package/native-src/src/librats/dht/traversal.cpp +326 -0
  130. package/native-src/src/librats/dht/traversal.h +120 -0
  131. package/native-src/src/librats/dht/udp_transport.cpp +49 -0
  132. package/native-src/src/librats/dht/udp_transport.h +51 -0
  133. package/native-src/src/librats/mdns/log.h +22 -0
  134. package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
  135. package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
  136. package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
  137. package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
  138. package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
  139. package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
  140. package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
  141. package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
  142. package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
  143. package/native-src/src/librats/node/circuit_service.h +84 -0
  144. package/native-src/src/librats/node/config.h +110 -0
  145. package/native-src/src/librats/node/dial_service.h +54 -0
  146. package/native-src/src/librats/node/dialer.cpp +264 -0
  147. package/native-src/src/librats/node/dialer.h +188 -0
  148. package/native-src/src/librats/node/host_events.h +26 -0
  149. package/native-src/src/librats/node/identify.cpp +130 -0
  150. package/native-src/src/librats/node/identify.h +71 -0
  151. package/native-src/src/librats/node/nat_status.cpp +103 -0
  152. package/native-src/src/librats/node/nat_status.h +118 -0
  153. package/native-src/src/librats/node/node.cpp +865 -0
  154. package/native-src/src/librats/node/node.h +344 -0
  155. package/native-src/src/librats/node/node_context.h +33 -0
  156. package/native-src/src/librats/node/peer_network.h +91 -0
  157. package/native-src/src/librats/peer/peer.h +49 -0
  158. package/native-src/src/librats/peer/peer_book.cpp +181 -0
  159. package/native-src/src/librats/peer/peer_book.h +88 -0
  160. package/native-src/src/librats/peer/peer_id.cpp +72 -0
  161. package/native-src/src/librats/peer/peer_id.h +62 -0
  162. package/native-src/src/librats/peer/peer_info.h +37 -0
  163. package/native-src/src/librats/peer/peer_table.cpp +170 -0
  164. package/native-src/src/librats/peer/peer_table.h +148 -0
  165. package/native-src/src/librats/security/handshaker.h +66 -0
  166. package/native-src/src/librats/security/identity.h +43 -0
  167. package/native-src/src/librats/security/noise_security.cpp +122 -0
  168. package/native-src/src/librats/security/noise_security.h +37 -0
  169. package/native-src/src/librats/security/plaintext_security.h +106 -0
  170. package/native-src/src/librats/security/session.h +37 -0
  171. package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
  172. package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
  173. package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
  174. package/native-src/src/librats/subsystems/bittorrent.h +136 -0
  175. package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
  176. package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
  177. package/native-src/src/librats/subsystems/dht_service.h +36 -0
  178. package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
  179. package/native-src/src/librats/subsystems/file_transfer.h +367 -0
  180. package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
  181. package/native-src/src/librats/subsystems/hole_punch.h +290 -0
  182. package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
  183. package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
  184. package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
  185. package/native-src/src/librats/subsystems/message_json.cpp +112 -0
  186. package/native-src/src/librats/subsystems/message_json.h +88 -0
  187. package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
  188. package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
  189. package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
  190. package/native-src/src/librats/subsystems/ping_service.h +66 -0
  191. package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
  192. package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
  193. package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
  194. package/native-src/src/librats/subsystems/pubsub.h +175 -0
  195. package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
  196. package/native-src/src/librats/subsystems/reconnection.h +126 -0
  197. package/native-src/src/librats/subsystems/relay.cpp +1142 -0
  198. package/native-src/src/librats/subsystems/relay.h +211 -0
  199. package/native-src/src/librats/subsystems/relay_service.h +46 -0
  200. package/native-src/src/librats/transport/connection.cpp +343 -0
  201. package/native-src/src/librats/transport/connection.h +283 -0
  202. package/native-src/src/librats/transport/link.h +96 -0
  203. package/native-src/src/librats/transport/reactor.cpp +588 -0
  204. package/native-src/src/librats/transport/reactor.h +262 -0
  205. package/native-src/src/librats/transport/reactor_pool.h +81 -0
  206. package/native-src/src/librats/transport/relay_link.cpp +208 -0
  207. package/native-src/src/librats/transport/relay_link.h +303 -0
  208. package/native-src/src/librats/transport/tcp_link.cpp +49 -0
  209. package/native-src/src/librats/transport/tcp_link.h +43 -0
  210. package/native-src/src/librats/transport/udp_mux.cpp +617 -0
  211. package/native-src/src/librats/transport/udp_mux.h +363 -0
  212. package/native-src/src/librats/transport/udp_packet.cpp +121 -0
  213. package/native-src/src/librats/transport/udp_packet.h +190 -0
  214. package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
  215. package/native-src/src/librats/transport/udp_stream.h +614 -0
  216. package/native-src/src/librats/util/features.h.in +51 -0
  217. package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
  218. package/native-src/src/librats/util/fs.h +136 -0
  219. package/native-src/src/librats/util/json.cpp +1002 -0
  220. package/native-src/src/librats/util/json.h +444 -0
  221. package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
  222. package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
  223. package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
  224. package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
  225. package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
  226. package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
  227. package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
  228. package/native-src/src/librats/util/rats_export.h +69 -0
  229. package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
  230. package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
  231. package/native-src/src/librats/wire/frame.cpp +76 -0
  232. package/native-src/src/librats/wire/frame.h +111 -0
  233. package/native-src/src/librats/wire/message_router.cpp +45 -0
  234. package/native-src/src/librats/wire/message_router.h +47 -0
  235. package/package.json +5 -4
  236. package/scripts/build-librats.js +1 -0
  237. package/scripts/postinstall.js +3 -3
  238. package/scripts/prepare-package.js +4 -4
  239. package/scripts/verify-installation.js +63 -105
  240. package/src/librats_node.cpp +1067 -1323
  241. package/native-src/src/bencode.cpp +0 -485
  242. package/native-src/src/bencode.h +0 -145
  243. package/native-src/src/bittorrent.cpp +0 -14
  244. package/native-src/src/bittorrent.h +0 -74
  245. package/native-src/src/bt_bitfield.cpp +0 -372
  246. package/native-src/src/bt_bitfield.h +0 -316
  247. package/native-src/src/bt_choker.cpp +0 -228
  248. package/native-src/src/bt_choker.h +0 -147
  249. package/native-src/src/bt_client.cpp +0 -1047
  250. package/native-src/src/bt_client.h +0 -445
  251. package/native-src/src/bt_create_torrent.cpp +0 -677
  252. package/native-src/src/bt_create_torrent.h +0 -473
  253. package/native-src/src/bt_extension.cpp +0 -469
  254. package/native-src/src/bt_extension.h +0 -309
  255. package/native-src/src/bt_file_storage.cpp +0 -261
  256. package/native-src/src/bt_file_storage.h +0 -298
  257. package/native-src/src/bt_handshake.cpp +0 -134
  258. package/native-src/src/bt_handshake.h +0 -157
  259. package/native-src/src/bt_messages.cpp +0 -364
  260. package/native-src/src/bt_messages.h +0 -324
  261. package/native-src/src/bt_network.cpp +0 -1007
  262. package/native-src/src/bt_network.h +0 -417
  263. package/native-src/src/bt_peer_connection.cpp +0 -742
  264. package/native-src/src/bt_peer_connection.h +0 -592
  265. package/native-src/src/bt_piece_picker.cpp +0 -786
  266. package/native-src/src/bt_piece_picker.h +0 -473
  267. package/native-src/src/bt_resume_data.cpp +0 -410
  268. package/native-src/src/bt_resume_data.h +0 -249
  269. package/native-src/src/bt_torrent.cpp +0 -2120
  270. package/native-src/src/bt_torrent.h +0 -641
  271. package/native-src/src/bt_torrent_info.cpp +0 -659
  272. package/native-src/src/bt_torrent_info.h +0 -418
  273. package/native-src/src/bt_types.h +0 -621
  274. package/native-src/src/chained_send_buffer.cpp +0 -75
  275. package/native-src/src/chained_send_buffer.h +0 -137
  276. package/native-src/src/crypto/hkdf.c +0 -266
  277. package/native-src/src/crypto/poly1305.h +0 -36
  278. package/native-src/src/dht.cpp +0 -3311
  279. package/native-src/src/dht.h +0 -717
  280. package/native-src/src/disk_io.cpp +0 -632
  281. package/native-src/src/disk_io.h +0 -315
  282. package/native-src/src/file_transfer.cpp +0 -1415
  283. package/native-src/src/file_transfer.h +0 -286
  284. package/native-src/src/fs.h +0 -108
  285. package/native-src/src/gossipsub.cpp +0 -1139
  286. package/native-src/src/gossipsub.h +0 -403
  287. package/native-src/src/ice.cpp +0 -893
  288. package/native-src/src/ice.h +0 -559
  289. package/native-src/src/json.hpp +0 -25526
  290. package/native-src/src/librats.cpp +0 -2378
  291. package/native-src/src/librats.h +0 -2324
  292. package/native-src/src/librats_bittorrent.cpp +0 -601
  293. package/native-src/src/librats_c.cpp +0 -1557
  294. package/native-src/src/librats_c.h +0 -323
  295. package/native-src/src/librats_discovery.cpp +0 -402
  296. package/native-src/src/librats_encryption.cpp +0 -275
  297. package/native-src/src/librats_file_transfer.cpp +0 -144
  298. package/native-src/src/librats_gossipsub.cpp +0 -289
  299. package/native-src/src/librats_ice.cpp +0 -213
  300. package/native-src/src/librats_log_macros.h +0 -36
  301. package/native-src/src/librats_logging.cpp +0 -173
  302. package/native-src/src/librats_mdns.cpp +0 -166
  303. package/native-src/src/librats_persistence.cpp +0 -796
  304. package/native-src/src/librats_portmap.cpp +0 -419
  305. package/native-src/src/librats_reconnection.cpp +0 -218
  306. package/native-src/src/librats_statistic.cpp +0 -105
  307. package/native-src/src/librats_storage.cpp +0 -189
  308. package/native-src/src/rats_export.h +0 -17
  309. package/native-src/src/receive_buffer.cpp +0 -82
  310. package/native-src/src/receive_buffer.h +0 -127
  311. package/native-src/src/socket.h +0 -228
  312. package/native-src/src/threadmanager.cpp +0 -105
  313. package/native-src/src/threadmanager.h +0 -53
  314. package/native-src/src/tracker.cpp +0 -1264
  315. package/native-src/src/tracker.h +0 -319
  316. package/native-src/src/turn.cpp +0 -762
  317. package/native-src/src/turn.h +0 -460
  318. package/native-src/src/wakeup_pipe.h +0 -60
  319. /package/native-src/src/{os.h → librats/util/os.h} +0 -0
@@ -1,1419 +1,1163 @@
1
+ /**
2
+ * LibRats Node.js native addon.
3
+ *
4
+ * N-API wrapper over the canonical C ABI in src/bindings/rats.h. Callbacks fire
5
+ * on librats' internal reactor thread, so every native callback marshals into
6
+ * the JS thread with a Napi::ThreadSafeFunction (TSFN). Per-channel / per-topic
7
+ * / per-json-type handlers are kept in maps owned by the RatsNode instance and
8
+ * torn down on destruction.
9
+ *
10
+ * Contract reminder (enforced by the C core, surfaced here as thrown errors):
11
+ * - Register callbacks and enable subsystems BEFORE start().
12
+ * - Calling an enable after start() -> RATS_ERR_ALREADY_STARTED.
13
+ * - Calling a subsystem op before its enable -> RATS_ERR_NOT_ENABLED.
14
+ */
15
+
1
16
  #include <napi.h>
2
- #include <iostream>
3
- #include <string>
17
+ #include <cstring>
4
18
  #include <memory>
5
- #include <unordered_map>
6
- #include "librats_c.h"
19
+ #include <string>
20
+ #include <vector>
21
+ #include <librats/bindings/rats.h>
7
22
 
8
23
  using namespace Napi;
9
24
 
10
- // Forward declarations
11
- class RatsClient;
25
+ namespace {
12
26
 
13
- // Global callback storage
14
- struct CallbackData {
15
- Napi::FunctionReference callback;
16
- Napi::Env env;
17
-
18
- CallbackData(Napi::Env environment) : env(environment) {}
19
- };
20
-
21
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> connection_callbacks;
22
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> string_callbacks;
23
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> binary_callbacks;
24
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> json_callbacks;
25
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> disconnect_callbacks;
26
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> file_progress_callbacks;
27
- std::unordered_map<rats_client_t, std::shared_ptr<CallbackData>> file_request_callbacks;
28
-
29
- // C callback wrappers
30
- void connection_callback_wrapper(void* user_data, const char* peer_id) {
31
- rats_client_t client = static_cast<rats_client_t>(user_data);
32
- auto it = connection_callbacks.find(client);
33
- if (it != connection_callbacks.end() && it->second) {
34
- auto callback_data = it->second;
35
- callback_data->callback.Call({Napi::String::New(callback_data->env, peer_id)});
36
- }
27
+ // Translate a rats_error_t into a JS exception when it is not RATS_OK.
28
+ // Returns true if an error was thrown (caller should bail out / return).
29
+ bool throw_on_error(Napi::Env env, rats_error_t err) {
30
+ if (err == RATS_OK) return false;
31
+ Napi::Error::New(env, std::string("librats: ") + rats_error_str(err))
32
+ .ThrowAsJavaScriptException();
33
+ return true;
37
34
  }
38
35
 
39
- void string_callback_wrapper(void* user_data, const char* peer_id, const char* message) {
40
- rats_client_t client = static_cast<rats_client_t>(user_data);
41
- auto it = string_callbacks.find(client);
42
- if (it != string_callbacks.end() && it->second) {
43
- auto callback_data = it->second;
44
- callback_data->callback.Call({
45
- Napi::String::New(callback_data->env, peer_id),
46
- Napi::String::New(callback_data->env, message)
47
- });
48
- }
49
- }
50
-
51
- void binary_callback_wrapper(void* user_data, const char* peer_id, const void* data, size_t size) {
52
- rats_client_t client = static_cast<rats_client_t>(user_data);
53
- auto it = binary_callbacks.find(client);
54
- if (it != binary_callbacks.end() && it->second) {
55
- auto callback_data = it->second;
56
- auto buffer = Napi::Buffer<uint8_t>::New(callback_data->env, size);
57
- memcpy(buffer.Data(), data, size);
58
- callback_data->callback.Call({
59
- Napi::String::New(callback_data->env, peer_id),
60
- buffer
61
- });
62
- }
63
- }
36
+ // Every instance method funnels through this: the C ABI does not null-check its
37
+ // handle, and destroy() legitimately leaves us without one. The variadic
38
+ // argument is the value to return — empty for a void method.
39
+ #define RATS_REQUIRE_NODE(...) \
40
+ do { \
41
+ if (!node_) { \
42
+ Napi::Error::New(info.Env(), "librats: node has been destroyed") \
43
+ .ThrowAsJavaScriptException(); \
44
+ return __VA_ARGS__; \
45
+ } \
46
+ } while (0)
64
47
 
65
- void json_callback_wrapper(void* user_data, const char* peer_id, const char* json_str) {
66
- rats_client_t client = static_cast<rats_client_t>(user_data);
67
- auto it = json_callbacks.find(client);
68
- if (it != json_callbacks.end() && it->second) {
69
- auto callback_data = it->second;
70
- callback_data->callback.Call({
71
- Napi::String::New(callback_data->env, peer_id),
72
- Napi::String::New(callback_data->env, json_str)
73
- });
74
- }
75
- }
48
+ } // namespace
76
49
 
77
- void disconnect_callback_wrapper(void* user_data, const char* peer_id) {
78
- rats_client_t client = static_cast<rats_client_t>(user_data);
79
- auto it = disconnect_callbacks.find(client);
80
- if (it != disconnect_callbacks.end() && it->second) {
81
- auto callback_data = it->second;
82
- callback_data->callback.Call({Napi::String::New(callback_data->env, peer_id)});
83
- }
84
- }
50
+ // ---------------------------------------------------------------------------
51
+ // Per-callback context. Each registration owns a TSFN plus a back-pointer used
52
+ // only for cleanup bookkeeping. The trampoline (the C function we hand to
53
+ // librats) receives the context as `user`.
54
+ // ---------------------------------------------------------------------------
85
55
 
86
- void file_progress_callback_wrapper(void* user_data, const char* transfer_id, int progress_percent, const char* status) {
87
- rats_client_t client = static_cast<rats_client_t>(user_data);
88
- auto it = file_progress_callbacks.find(client);
89
- if (it != file_progress_callbacks.end() && it->second) {
90
- auto callback_data = it->second;
91
- callback_data->callback.Call({
92
- Napi::String::New(callback_data->env, transfer_id),
93
- Napi::Number::New(callback_data->env, progress_percent),
94
- Napi::String::New(callback_data->env, status)
95
- });
96
- }
97
- }
56
+ struct CbContext {
57
+ Napi::ThreadSafeFunction tsfn;
58
+ bool acquired = false;
98
59
 
99
- // Fires for every incoming transfer offer (file or directory). The app should
100
- // respond by calling acceptFileTransfer()/rejectFileTransfer(). remote_path is
101
- // always empty in the push-only model, so only filename is forwarded.
102
- void file_request_callback_wrapper(void* user_data, const char* peer_id, const char* transfer_id, const char* remote_path, const char* filename) {
103
- (void)remote_path;
104
- rats_client_t client = static_cast<rats_client_t>(user_data);
105
- auto it = file_request_callbacks.find(client);
106
- if (it != file_request_callbacks.end() && it->second) {
107
- auto callback_data = it->second;
108
- callback_data->callback.Call({
109
- Napi::String::New(callback_data->env, peer_id),
110
- Napi::String::New(callback_data->env, transfer_id),
111
- Napi::String::New(callback_data->env, filename)
112
- });
60
+ void init(Napi::Env env, const Napi::Function& fn, const char* name) {
61
+ release();
62
+ tsfn = Napi::ThreadSafeFunction::New(env, fn, name, 0, 1);
63
+ acquired = true;
64
+ }
65
+ void release() {
66
+ if (acquired) {
67
+ tsfn.Release();
68
+ acquired = false;
69
+ }
113
70
  }
114
- }
71
+ };
115
72
 
116
- class RatsClient : public Napi::ObjectWrap<RatsClient> {
73
+ class RatsNode : public Napi::ObjectWrap<RatsNode> {
117
74
  public:
118
- static Napi::Object Init(Napi::Env env, Napi::Object exports) {
119
- Napi::Function func = DefineClass(env, "RatsClient", {
120
- InstanceMethod("start", &RatsClient::Start),
121
- InstanceMethod("stop", &RatsClient::Stop),
122
- InstanceMethod("connect", &RatsClient::Connect),
123
- InstanceMethod("disconnect", &RatsClient::Disconnect),
124
- InstanceMethod("broadcastString", &RatsClient::BroadcastString),
125
- InstanceMethod("sendString", &RatsClient::SendString),
126
- InstanceMethod("broadcastBinary", &RatsClient::BroadcastBinary),
127
- InstanceMethod("sendBinary", &RatsClient::SendBinary),
128
- InstanceMethod("broadcastJson", &RatsClient::BroadcastJson),
129
- InstanceMethod("sendJson", &RatsClient::SendJson),
130
- InstanceMethod("getPeerCount", &RatsClient::GetPeerCount),
131
- InstanceMethod("getListenPort", &RatsClient::GetListenPort),
132
- InstanceMethod("getOurPeerId", &RatsClient::GetOurPeerId),
133
- InstanceMethod("getPeerIds", &RatsClient::GetPeerIds),
134
- InstanceMethod("getConnectionStatistics", &RatsClient::GetConnectionStatistics),
135
- InstanceMethod("setMaxPeers", &RatsClient::SetMaxPeers),
136
- InstanceMethod("getMaxPeers", &RatsClient::GetMaxPeers),
137
- InstanceMethod("isPeerLimitReached", &RatsClient::IsPeerLimitReached),
138
-
139
- // DHT methods
140
- InstanceMethod("startDhtDiscovery", &RatsClient::StartDhtDiscovery),
141
- InstanceMethod("stopDhtDiscovery", &RatsClient::StopDhtDiscovery),
142
- InstanceMethod("isDhtRunning", &RatsClient::IsDhtRunning),
143
- InstanceMethod("announceForHash", &RatsClient::AnnounceForHash),
144
- InstanceMethod("getDhtRoutingTableSize", &RatsClient::GetDhtRoutingTableSize),
145
-
146
- // mDNS methods
147
- InstanceMethod("startMdnsDiscovery", &RatsClient::StartMdnsDiscovery),
148
- InstanceMethod("stopMdnsDiscovery", &RatsClient::StopMdnsDiscovery),
149
- InstanceMethod("isMdnsRunning", &RatsClient::IsMdnsRunning),
150
- InstanceMethod("queryMdnsServices", &RatsClient::QueryMdnsServices),
151
-
152
- // Basic Encryption methods
153
- InstanceMethod("setEncryptionEnabled", &RatsClient::SetEncryptionEnabled),
154
- InstanceMethod("isEncryptionEnabled", &RatsClient::IsEncryptionEnabled),
155
-
156
- // Enhanced Encryption methods
157
- InstanceMethod("initializeEncryption", &RatsClient::InitializeEncryption),
158
- InstanceMethod("isPeerEncrypted", &RatsClient::IsPeerEncrypted),
159
- InstanceMethod("setNoiseStaticKeypair", &RatsClient::SetNoiseStaticKeypair),
160
- InstanceMethod("getNoiseStaticPublicKey", &RatsClient::GetNoiseStaticPublicKey),
161
- InstanceMethod("getPeerNoisePublicKey", &RatsClient::GetPeerNoisePublicKey),
162
- InstanceMethod("getPeerHandshakeHash", &RatsClient::GetPeerHandshakeHash),
163
-
164
- // ICE (NAT Traversal) methods
165
- InstanceMethod("isIceAvailable", &RatsClient::IsIceAvailable),
166
- InstanceMethod("addStunServer", &RatsClient::AddStunServer),
167
- InstanceMethod("addTurnServer", &RatsClient::AddTurnServer),
168
- InstanceMethod("clearIceServers", &RatsClient::ClearIceServers),
169
- InstanceMethod("gatherIceCandidates", &RatsClient::GatherIceCandidates),
170
- InstanceMethod("getIceCandidates", &RatsClient::GetIceCandidates),
171
- InstanceMethod("isIceGatheringComplete", &RatsClient::IsIceGatheringComplete),
172
- InstanceMethod("getPublicAddress", &RatsClient::GetPublicAddress),
173
- InstanceMethod("discoverPublicAddress", &RatsClient::DiscoverPublicAddress),
174
- InstanceMethod("addRemoteIceCandidate", &RatsClient::AddRemoteIceCandidate),
175
- InstanceMethod("endOfRemoteIceCandidates", &RatsClient::EndOfRemoteIceCandidates),
176
- InstanceMethod("startIceChecks", &RatsClient::StartIceChecks),
177
- InstanceMethod("getIceConnectionState", &RatsClient::GetIceConnectionState),
178
- InstanceMethod("getIceGatheringState", &RatsClient::GetIceGatheringState),
179
- InstanceMethod("isIceConnected", &RatsClient::IsIceConnected),
180
- InstanceMethod("getIceSelectedPair", &RatsClient::GetIceSelectedPair),
181
- InstanceMethod("closeIce", &RatsClient::CloseIce),
182
- InstanceMethod("restartIce", &RatsClient::RestartIce),
183
-
184
- // GossipSub methods
185
- InstanceMethod("isGossipsubAvailable", &RatsClient::IsGossipsubAvailable),
186
- InstanceMethod("isGossipsubRunning", &RatsClient::IsGossipsubRunning),
187
- InstanceMethod("subscribeToTopic", &RatsClient::SubscribeToTopic),
188
- InstanceMethod("unsubscribeFromTopic", &RatsClient::UnsubscribeFromTopic),
189
- InstanceMethod("isSubscribedToTopic", &RatsClient::IsSubscribedToTopic),
190
- InstanceMethod("getSubscribedTopics", &RatsClient::GetSubscribedTopics),
191
- InstanceMethod("publishToTopic", &RatsClient::PublishToTopic),
192
- InstanceMethod("publishJsonToTopic", &RatsClient::PublishJsonToTopic),
193
- InstanceMethod("getTopicPeers", &RatsClient::GetTopicPeers),
194
- InstanceMethod("getGossipsubStatistics", &RatsClient::GetGossipsubStatistics),
195
-
196
- // File Transfer methods
197
- InstanceMethod("sendFile", &RatsClient::SendFile),
198
- InstanceMethod("sendDirectory", &RatsClient::SendDirectory),
199
- InstanceMethod("acceptFileTransfer", &RatsClient::AcceptFileTransfer),
200
- InstanceMethod("rejectFileTransfer", &RatsClient::RejectFileTransfer),
201
- InstanceMethod("cancelFileTransfer", &RatsClient::CancelFileTransfer),
202
- InstanceMethod("pauseFileTransfer", &RatsClient::PauseFileTransfer),
203
- InstanceMethod("resumeFileTransfer", &RatsClient::ResumeFileTransfer),
204
- InstanceMethod("getFileTransferProgress", &RatsClient::GetFileTransferProgress),
205
- InstanceMethod("getFileTransferStatistics", &RatsClient::GetFileTransferStatistics),
206
-
207
- // Address blocking
208
- InstanceMethod("addIgnoredAddress", &RatsClient::AddIgnoredAddress),
209
-
210
- // Callback methods
211
- InstanceMethod("onConnection", &RatsClient::OnConnection),
212
- InstanceMethod("onString", &RatsClient::OnString),
213
- InstanceMethod("onBinary", &RatsClient::OnBinary),
214
- InstanceMethod("onJson", &RatsClient::OnJson),
215
- InstanceMethod("onDisconnect", &RatsClient::OnDisconnect),
216
- InstanceMethod("onFileProgress", &RatsClient::OnFileProgress),
217
- InstanceMethod("onFileRequest", &RatsClient::OnFileRequest),
218
-
219
- // Configuration persistence
220
- InstanceMethod("loadConfiguration", &RatsClient::LoadConfiguration),
221
- InstanceMethod("saveConfiguration", &RatsClient::SaveConfiguration),
222
- InstanceMethod("setDataDirectory", &RatsClient::SetDataDirectory),
223
- InstanceMethod("getDataDirectory", &RatsClient::GetDataDirectory)
224
- });
225
-
226
- exports.Set("RatsClient", func);
227
- return exports;
228
- }
229
-
230
- RatsClient(const Napi::CallbackInfo& info) : Napi::ObjectWrap<RatsClient>(info), client_(nullptr) {
231
- Napi::Env env = info.Env();
232
-
233
- if (info.Length() < 1 || !info[0].IsNumber()) {
234
- Napi::TypeError::New(env, "Expected port number").ThrowAsJavaScriptException();
235
- return;
236
- }
237
-
238
- int port = info[0].As<Napi::Number>().Int32Value();
239
- if (port < 0 || port > 65535) {
240
- Napi::RangeError::New(env, "Port number must be between 0 and 65535").ThrowAsJavaScriptException();
241
- return;
242
- }
243
- client_ = rats_create(port);
244
-
245
- if (!client_) {
246
- Napi::Error::New(env, "Failed to create RatsClient").ThrowAsJavaScriptException();
247
- return;
248
- }
249
- }
250
-
251
- ~RatsClient() {
252
- if (client_ != nullptr) {
253
- // Clean up callbacks
254
- connection_callbacks.erase(client_);
255
- string_callbacks.erase(client_);
256
- binary_callbacks.erase(client_);
257
- json_callbacks.erase(client_);
258
- disconnect_callbacks.erase(client_);
259
- file_progress_callbacks.erase(client_);
260
- file_request_callbacks.erase(client_);
261
-
262
- rats_destroy(client_);
263
- }
264
- }
75
+ static Napi::Object Init(Napi::Env env, Napi::Object exports);
76
+ RatsNode(const Napi::CallbackInfo& info);
77
+ ~RatsNode();
265
78
 
266
79
  private:
267
- rats_client_t client_;
268
-
269
- // Basic operations
270
- Napi::Value Start(const Napi::CallbackInfo& info) {
271
- Napi::Env env = info.Env();
272
- int result = rats_start(client_);
273
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
274
- }
275
-
276
- void Stop(const Napi::CallbackInfo& info) {
277
- if (client_ != nullptr) {
278
- rats_stop(client_);
279
- }
280
- }
281
-
282
- Napi::Value Connect(const Napi::CallbackInfo& info) {
283
- Napi::Env env = info.Env();
284
-
285
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
286
- Napi::TypeError::New(env, "Expected host (string) and port (number)").ThrowAsJavaScriptException();
287
- return env.Null();
288
- }
289
-
290
- std::string host = info[0].As<Napi::String>().Utf8Value();
291
- int port = info[1].As<Napi::Number>().Int32Value();
292
-
293
- int result = rats_connect(client_, host.c_str(), port);
294
- return Napi::Boolean::New(env, result == 1);
295
- }
296
-
297
- void Disconnect(const Napi::CallbackInfo& info) {
298
- if (info.Length() < 1 || !info[0].IsString()) {
299
- Napi::TypeError::New(info.Env(), "Expected peer_id (string)").ThrowAsJavaScriptException();
300
- return;
301
- }
302
-
303
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
304
- rats_disconnect_peer_by_id(client_, peer_id.c_str());
305
- }
306
-
307
- Napi::Value BroadcastString(const Napi::CallbackInfo& info) {
308
- Napi::Env env = info.Env();
309
-
310
- if (info.Length() < 1 || !info[0].IsString()) {
311
- Napi::TypeError::New(env, "Expected message (string)").ThrowAsJavaScriptException();
312
- return env.Null();
313
- }
314
-
315
- std::string message = info[0].As<Napi::String>().Utf8Value();
316
- int result = rats_broadcast_string(client_, message.c_str());
317
- return Napi::Number::New(env, result);
318
- }
319
-
320
- Napi::Value SendString(const Napi::CallbackInfo& info) {
321
- Napi::Env env = info.Env();
322
-
323
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
324
- Napi::TypeError::New(env, "Expected peer_id (string) and message (string)").ThrowAsJavaScriptException();
325
- return env.Null();
326
- }
327
-
328
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
329
- std::string message = info[1].As<Napi::String>().Utf8Value();
330
-
331
- int result = rats_send_string(client_, peer_id.c_str(), message.c_str());
332
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
333
- }
334
-
335
- Napi::Value BroadcastBinary(const Napi::CallbackInfo& info) {
336
- Napi::Env env = info.Env();
337
-
338
- if (info.Length() < 1 || !info[0].IsBuffer()) {
339
- Napi::TypeError::New(env, "Expected data (Buffer)").ThrowAsJavaScriptException();
340
- return env.Null();
341
- }
342
-
343
- Napi::Buffer<uint8_t> buffer = info[0].As<Napi::Buffer<uint8_t>>();
344
- int result = rats_broadcast_binary(client_, buffer.Data(), buffer.Length());
345
- return Napi::Number::New(env, result);
346
- }
347
-
348
- Napi::Value SendBinary(const Napi::CallbackInfo& info) {
349
- Napi::Env env = info.Env();
350
-
351
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsBuffer()) {
352
- Napi::TypeError::New(env, "Expected peer_id (string) and data (Buffer)").ThrowAsJavaScriptException();
353
- return env.Null();
354
- }
355
-
356
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
357
- Napi::Buffer<uint8_t> buffer = info[1].As<Napi::Buffer<uint8_t>>();
358
-
359
- rats_error_t result = rats_send_binary(client_, peer_id.c_str(), buffer.Data(), buffer.Length());
360
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
361
- }
362
-
363
- Napi::Value BroadcastJson(const Napi::CallbackInfo& info) {
364
- Napi::Env env = info.Env();
365
-
366
- if (info.Length() < 1 || !info[0].IsString()) {
367
- Napi::TypeError::New(env, "Expected json_str (string)").ThrowAsJavaScriptException();
368
- return env.Null();
369
- }
370
-
371
- std::string json_str = info[0].As<Napi::String>().Utf8Value();
372
- int result = rats_broadcast_json(client_, json_str.c_str());
373
- return Napi::Number::New(env, result);
374
- }
375
-
376
- Napi::Value SendJson(const Napi::CallbackInfo& info) {
377
- Napi::Env env = info.Env();
378
-
379
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
380
- Napi::TypeError::New(env, "Expected peer_id (string) and json_str (string)").ThrowAsJavaScriptException();
381
- return env.Null();
382
- }
383
-
384
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
385
- std::string json_str = info[1].As<Napi::String>().Utf8Value();
386
-
387
- rats_error_t result = rats_send_json(client_, peer_id.c_str(), json_str.c_str());
388
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
389
- }
390
-
391
- // Info methods
392
- Napi::Value GetPeerCount(const Napi::CallbackInfo& info) {
393
- Napi::Env env = info.Env();
394
- int count = rats_get_peer_count(client_);
395
- return Napi::Number::New(env, count);
396
- }
397
-
398
- Napi::Value GetListenPort(const Napi::CallbackInfo& info) {
399
- Napi::Env env = info.Env();
400
- int port = rats_get_listen_port(client_);
401
- return Napi::Number::New(env, port);
402
- }
403
-
404
- Napi::Value GetOurPeerId(const Napi::CallbackInfo& info) {
405
- Napi::Env env = info.Env();
406
- char* peer_id = rats_get_our_peer_id(client_);
407
- if (!peer_id) return env.Null();
408
-
409
- Napi::String result = Napi::String::New(env, peer_id);
410
- rats_string_free(peer_id);
411
- return result;
412
- }
413
-
414
- Napi::Value GetPeerIds(const Napi::CallbackInfo& info) {
415
- Napi::Env env = info.Env();
416
- int count = 0;
417
- char** peer_ids = rats_get_peer_ids(client_, &count);
418
-
419
- if (!peer_ids || count == 0) {
420
- return Napi::Array::New(env, 0);
421
- }
422
-
423
- Napi::Array result = Napi::Array::New(env, count);
424
- for (int i = 0; i < count; i++) {
425
- result[i] = Napi::String::New(env, peer_ids[i]);
426
- rats_string_free(peer_ids[i]);
427
- }
428
- free(peer_ids);
429
-
430
- return result;
431
- }
432
-
433
- Napi::Value GetConnectionStatistics(const Napi::CallbackInfo& info) {
434
- Napi::Env env = info.Env();
435
- char* stats_json = rats_get_connection_statistics_json(client_);
436
- if (!stats_json) return env.Null();
437
-
438
- Napi::String result = Napi::String::New(env, stats_json);
439
- rats_string_free(stats_json);
440
- return result;
441
- }
442
-
443
- // Peer configuration
444
- Napi::Value SetMaxPeers(const Napi::CallbackInfo& info) {
445
- Napi::Env env = info.Env();
446
-
447
- if (info.Length() < 1 || !info[0].IsNumber()) {
448
- Napi::TypeError::New(env, "Expected max_peers (number)").ThrowAsJavaScriptException();
449
- return env.Null();
450
- }
451
-
452
- int max_peers = info[0].As<Napi::Number>().Int32Value();
453
- rats_error_t result = rats_set_max_peers(client_, max_peers);
454
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
455
- }
456
-
457
- Napi::Value GetMaxPeers(const Napi::CallbackInfo& info) {
458
- Napi::Env env = info.Env();
459
- int max_peers = rats_get_max_peers(client_);
460
- return Napi::Number::New(env, max_peers);
461
- }
462
-
463
- Napi::Value IsPeerLimitReached(const Napi::CallbackInfo& info) {
464
- Napi::Env env = info.Env();
465
- int reached = rats_is_peer_limit_reached(client_);
466
- return Napi::Boolean::New(env, reached != 0);
467
- }
468
-
469
- // DHT methods
470
- Napi::Value StartDhtDiscovery(const Napi::CallbackInfo& info) {
471
- Napi::Env env = info.Env();
472
-
473
- if (info.Length() < 1 || !info[0].IsNumber()) {
474
- Napi::TypeError::New(env, "Expected dht_port (number)").ThrowAsJavaScriptException();
475
- return env.Null();
476
- }
477
-
478
- int dht_port = info[0].As<Napi::Number>().Int32Value();
479
- rats_error_t result = rats_start_dht_discovery(client_, dht_port);
480
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
481
- }
482
-
483
- void StopDhtDiscovery(const Napi::CallbackInfo& info) {
484
- rats_stop_dht_discovery(client_);
485
- }
486
-
487
- Napi::Value IsDhtRunning(const Napi::CallbackInfo& info) {
488
- Napi::Env env = info.Env();
489
- int running = rats_is_dht_running(client_);
490
- return Napi::Boolean::New(env, running != 0);
491
- }
492
-
493
- Napi::Value AnnounceForHash(const Napi::CallbackInfo& info) {
494
- Napi::Env env = info.Env();
495
-
496
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
497
- Napi::TypeError::New(env, "Expected content_hash (string) and port (number)").ThrowAsJavaScriptException();
498
- return env.Null();
499
- }
500
-
501
- std::string content_hash = info[0].As<Napi::String>().Utf8Value();
502
- int port = info[1].As<Napi::Number>().Int32Value();
503
-
504
- // Check if optional callback is provided
505
- rats_peers_found_cb c_callback = nullptr;
506
- void* callback_user_data = nullptr;
507
- Napi::ThreadSafeFunction* tsfn_ptr = nullptr;
508
-
509
- if (info.Length() >= 3 && info[2].IsFunction()) {
510
- // Create thread-safe function for callback
511
- auto tsfn = new Napi::ThreadSafeFunction();
512
- *tsfn = Napi::ThreadSafeFunction::New(
513
- env,
514
- info[2].As<Napi::Function>(),
515
- "AnnounceForHashCallback",
516
- 0,
517
- 1,
518
- [](Napi::Env) {} // Release callback
519
- );
520
- tsfn_ptr = tsfn;
521
-
522
- c_callback = [](void* user_data, const char** peer_addresses, int count) {
523
- auto* tsfn = static_cast<Napi::ThreadSafeFunction*>(user_data);
524
- if (!tsfn) return;
525
-
526
- // Copy peer addresses for async callback
527
- std::vector<std::string>* peers = new std::vector<std::string>();
528
- for (int i = 0; i < count; i++) {
529
- if (peer_addresses[i]) {
530
- peers->push_back(peer_addresses[i]);
531
- }
532
- }
533
-
534
- tsfn->BlockingCall(peers, [](Napi::Env env, Napi::Function jsCallback, std::vector<std::string>* data) {
535
- Napi::Array arr = Napi::Array::New(env, data->size());
536
- for (size_t i = 0; i < data->size(); i++) {
537
- arr.Set(static_cast<uint32_t>(i), Napi::String::New(env, (*data)[i]));
538
- }
539
- jsCallback.Call({arr});
540
- delete data;
541
- });
542
-
543
- tsfn->Release();
544
- delete tsfn;
545
- };
546
- callback_user_data = tsfn_ptr;
547
- }
548
-
549
- rats_error_t result = rats_announce_for_hash(client_, content_hash.c_str(), port, c_callback, callback_user_data);
550
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
551
- }
552
-
553
- Napi::Value GetDhtRoutingTableSize(const Napi::CallbackInfo& info) {
554
- Napi::Env env = info.Env();
555
- size_t size = rats_get_dht_routing_table_size(client_);
556
- return Napi::Number::New(env, static_cast<double>(size));
557
- }
558
-
559
- // mDNS methods
560
- Napi::Value StartMdnsDiscovery(const Napi::CallbackInfo& info) {
561
- Napi::Env env = info.Env();
562
-
563
- std::string service;
564
- const char* service_name = nullptr;
565
- if (info.Length() > 0 && info[0].IsString()) {
566
- service = info[0].As<Napi::String>().Utf8Value();
567
- service_name = service.c_str();
568
- }
569
-
570
- rats_error_t result = rats_start_mdns_discovery(client_, service_name);
571
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
572
- }
573
-
574
- void StopMdnsDiscovery(const Napi::CallbackInfo& info) {
575
- rats_stop_mdns_discovery(client_);
576
- }
577
-
578
- Napi::Value IsMdnsRunning(const Napi::CallbackInfo& info) {
579
- Napi::Env env = info.Env();
580
- int running = rats_is_mdns_running(client_);
581
- return Napi::Boolean::New(env, running != 0);
582
- }
583
-
584
- Napi::Value QueryMdnsServices(const Napi::CallbackInfo& info) {
585
- Napi::Env env = info.Env();
586
- rats_error_t result = rats_query_mdns_services(client_);
587
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
588
- }
589
-
590
- // Encryption methods
591
- Napi::Value SetEncryptionEnabled(const Napi::CallbackInfo& info) {
592
- Napi::Env env = info.Env();
593
-
594
- if (info.Length() < 1 || !info[0].IsBoolean()) {
595
- Napi::TypeError::New(env, "Expected enabled (boolean)").ThrowAsJavaScriptException();
596
- return env.Null();
597
- }
598
-
599
- bool enabled = info[0].As<Napi::Boolean>().Value();
600
- rats_error_t result = rats_set_encryption_enabled(client_, enabled ? 1 : 0);
601
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
602
- }
603
-
604
- Napi::Value IsEncryptionEnabled(const Napi::CallbackInfo& info) {
605
- Napi::Env env = info.Env();
606
- int enabled = rats_is_encryption_enabled(client_);
607
- return Napi::Boolean::New(env, enabled != 0);
608
- }
609
-
610
- // ===================== ENHANCED ENCRYPTION METHODS =====================
611
-
612
- Napi::Value InitializeEncryption(const Napi::CallbackInfo& info) {
613
- Napi::Env env = info.Env();
614
-
615
- if (info.Length() < 1 || !info[0].IsBoolean()) {
616
- Napi::TypeError::New(env, "Expected enable (boolean)").ThrowAsJavaScriptException();
617
- return env.Null();
618
- }
619
-
620
- bool enable = info[0].As<Napi::Boolean>().Value();
621
- rats_error_t result = rats_initialize_encryption(client_, enable ? 1 : 0);
622
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
623
- }
624
-
625
- Napi::Value IsPeerEncrypted(const Napi::CallbackInfo& info) {
626
- Napi::Env env = info.Env();
627
-
628
- if (info.Length() < 1 || !info[0].IsString()) {
629
- Napi::TypeError::New(env, "Expected peer_id (string)").ThrowAsJavaScriptException();
630
- return env.Null();
631
- }
632
-
633
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
634
- int encrypted = rats_is_peer_encrypted(client_, peer_id.c_str());
635
- return Napi::Boolean::New(env, encrypted != 0);
636
- }
637
-
638
- Napi::Value SetNoiseStaticKeypair(const Napi::CallbackInfo& info) {
639
- Napi::Env env = info.Env();
640
-
641
- if (info.Length() < 1 || !info[0].IsString()) {
642
- Napi::TypeError::New(env, "Expected private_key_hex (string)").ThrowAsJavaScriptException();
643
- return env.Null();
644
- }
645
-
646
- std::string private_key_hex = info[0].As<Napi::String>().Utf8Value();
647
- rats_error_t result = rats_set_noise_static_keypair(client_, private_key_hex.c_str());
648
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
649
- }
650
-
651
- Napi::Value GetNoiseStaticPublicKey(const Napi::CallbackInfo& info) {
652
- Napi::Env env = info.Env();
653
- char* key = rats_get_noise_static_public_key(client_);
654
- if (!key) return env.Null();
655
-
656
- Napi::String result = Napi::String::New(env, key);
657
- rats_string_free(key);
658
- return result;
659
- }
660
-
661
- Napi::Value GetPeerNoisePublicKey(const Napi::CallbackInfo& info) {
662
- Napi::Env env = info.Env();
663
-
664
- if (info.Length() < 1 || !info[0].IsString()) {
665
- Napi::TypeError::New(env, "Expected peer_id (string)").ThrowAsJavaScriptException();
666
- return env.Null();
667
- }
668
-
669
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
670
- char* key = rats_get_peer_noise_public_key(client_, peer_id.c_str());
671
- if (!key) return env.Null();
672
-
673
- Napi::String result = Napi::String::New(env, key);
674
- rats_string_free(key);
675
- return result;
676
- }
677
-
678
- Napi::Value GetPeerHandshakeHash(const Napi::CallbackInfo& info) {
679
- Napi::Env env = info.Env();
680
-
681
- if (info.Length() < 1 || !info[0].IsString()) {
682
- Napi::TypeError::New(env, "Expected peer_id (string)").ThrowAsJavaScriptException();
683
- return env.Null();
684
- }
685
-
686
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
687
- char* hash = rats_get_peer_handshake_hash(client_, peer_id.c_str());
688
- if (!hash) return env.Null();
689
-
690
- Napi::String result = Napi::String::New(env, hash);
691
- rats_string_free(hash);
692
- return result;
693
- }
694
-
695
- // ===================== ICE (NAT TRAVERSAL) METHODS =====================
696
-
697
- Napi::Value IsIceAvailable(const Napi::CallbackInfo& info) {
698
- Napi::Env env = info.Env();
699
- int available = rats_is_ice_available(client_);
700
- return Napi::Boolean::New(env, available != 0);
701
- }
702
-
703
- void AddStunServer(const Napi::CallbackInfo& info) {
704
- if (info.Length() < 1 || !info[0].IsString()) {
705
- Napi::TypeError::New(info.Env(), "Expected host (string)").ThrowAsJavaScriptException();
706
- return;
707
- }
708
-
709
- std::string host = info[0].As<Napi::String>().Utf8Value();
710
- uint16_t port = 3478;
711
- if (info.Length() > 1 && info[1].IsNumber()) {
712
- port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
713
- }
714
-
715
- rats_add_stun_server(client_, host.c_str(), port);
716
- }
717
-
718
- void AddTurnServer(const Napi::CallbackInfo& info) {
719
- if (info.Length() < 4 || !info[0].IsString() || !info[1].IsNumber() ||
720
- !info[2].IsString() || !info[3].IsString()) {
721
- Napi::TypeError::New(info.Env(), "Expected host, port, username, password").ThrowAsJavaScriptException();
722
- return;
723
- }
724
-
725
- std::string host = info[0].As<Napi::String>().Utf8Value();
726
- uint16_t port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
727
- std::string username = info[2].As<Napi::String>().Utf8Value();
728
- std::string password = info[3].As<Napi::String>().Utf8Value();
729
-
730
- rats_add_turn_server(client_, host.c_str(), port, username.c_str(), password.c_str());
731
- }
732
-
733
- void ClearIceServers(const Napi::CallbackInfo& info) {
734
- rats_clear_ice_servers(client_);
735
- }
736
-
737
- Napi::Value GatherIceCandidates(const Napi::CallbackInfo& info) {
738
- Napi::Env env = info.Env();
739
- int result = rats_gather_ice_candidates(client_);
740
- return Napi::Boolean::New(env, result != 0);
741
- }
742
-
743
- Napi::Value GetIceCandidates(const Napi::CallbackInfo& info) {
744
- Napi::Env env = info.Env();
745
- char* candidates_json = rats_get_ice_candidates_json(client_);
746
- if (!candidates_json) return env.Null();
747
-
748
- Napi::String result = Napi::String::New(env, candidates_json);
749
- rats_string_free(candidates_json);
750
- return result;
751
- }
752
-
753
- Napi::Value IsIceGatheringComplete(const Napi::CallbackInfo& info) {
754
- Napi::Env env = info.Env();
755
- int complete = rats_is_ice_gathering_complete(client_);
756
- return Napi::Boolean::New(env, complete != 0);
757
- }
758
-
759
- Napi::Value GetPublicAddress(const Napi::CallbackInfo& info) {
760
- Napi::Env env = info.Env();
761
- char* address = rats_get_public_address(client_);
762
- if (!address) return env.Null();
763
-
764
- Napi::String result = Napi::String::New(env, address);
765
- rats_string_free(address);
766
- return result;
80
+ rats_t node_ = nullptr;
81
+
82
+ // Single-slot callbacks (peer connect/disconnect, file offer/progress/complete).
83
+ std::unique_ptr<CbContext> on_connected_;
84
+ std::unique_ptr<CbContext> on_disconnected_;
85
+ std::unique_ptr<CbContext> on_file_offer_;
86
+ std::unique_ptr<CbContext> on_file_progress_;
87
+ std::unique_ptr<CbContext> on_file_complete_;
88
+
89
+ // Multi-slot callbacks keyed by channel / topic / json type. We keep them
90
+ // alive for the lifetime of the client; the trampolines look up nothing —
91
+ // each registration has its own CbContext handed to librats as `user`.
92
+ std::vector<std::unique_ptr<CbContext>> handlers_;
93
+
94
+ CbContext* new_handler() {
95
+ handlers_.push_back(std::make_unique<CbContext>());
96
+ return handlers_.back().get();
767
97
  }
768
-
769
- Napi::Value DiscoverPublicAddress(const Napi::CallbackInfo& info) {
770
- Napi::Env env = info.Env();
771
-
772
- std::string server_str;
773
- const char* server = nullptr;
774
- uint16_t port = 0;
775
- int timeout_ms = 5000;
776
-
777
- if (info.Length() > 0 && info[0].IsString()) {
778
- server_str = info[0].As<Napi::String>().Utf8Value();
779
- server = server_str.c_str();
98
+
99
+ // Release TSFNs + the native node. Idempotent; used by ~RatsNode and destroy().
100
+ void Teardown();
101
+
102
+ // ---- lifecycle / core ----
103
+ void Start(const Napi::CallbackInfo& info);
104
+ void Stop(const Napi::CallbackInfo& info);
105
+ void Destroy(const Napi::CallbackInfo& info);
106
+ Napi::Value GetListenPort(const Napi::CallbackInfo& info);
107
+ Napi::Value GetLocalId(const Napi::CallbackInfo& info);
108
+ Napi::Value GetProtocol(const Napi::CallbackInfo& info);
109
+ Napi::Value GetTransports(const Napi::CallbackInfo& info);
110
+
111
+ // ---- connections ----
112
+ Napi::Value Connect(const Napi::CallbackInfo& info);
113
+ Napi::Value GetPeerCount(const Napi::CallbackInfo& info);
114
+ Napi::Value GetPeerIds(const Napi::CallbackInfo& info);
115
+ void SetMaxPeers(const Napi::CallbackInfo& info);
116
+ Napi::Value GetMaxPeers(const Napi::CallbackInfo& info);
117
+ Napi::Value GetPeerTransport(const Napi::CallbackInfo& info);
118
+ Napi::Value GetPeerTransports(const Napi::CallbackInfo& info);
119
+
120
+ // ---- raw channel messaging ----
121
+ Napi::Value Send(const Napi::CallbackInfo& info);
122
+ Napi::Value Broadcast(const Napi::CallbackInfo& info);
123
+ void On(const Napi::CallbackInfo& info);
124
+
125
+ // ---- peer events ----
126
+ void OnPeerConnected(const Napi::CallbackInfo& info);
127
+ void OnPeerDisconnected(const Napi::CallbackInfo& info);
128
+
129
+ // ---- discovery / NAT ----
130
+ void EnableDht(const Napi::CallbackInfo& info);
131
+ void EnableMdns(const Napi::CallbackInfo& info);
132
+ void EnablePortMapping(const Napi::CallbackInfo& info);
133
+ void EnableHolePunch(const Napi::CallbackInfo& info);
134
+ Napi::Value PunchPeer(const Napi::CallbackInfo& info);
135
+ void EnableRelay(const Napi::CallbackInfo& info);
136
+ Napi::Value ConnectViaRelay(const Napi::CallbackInfo& info);
137
+ Napi::Value GetNatMapping(const Napi::CallbackInfo& info);
138
+
139
+ // ---- pub/sub ----
140
+ void EnablePubsub(const Napi::CallbackInfo& info);
141
+ void Subscribe(const Napi::CallbackInfo& info);
142
+ void Unsubscribe(const Napi::CallbackInfo& info);
143
+ Napi::Value Publish(const Napi::CallbackInfo& info);
144
+
145
+ // ---- typed JSON ----
146
+ void EnableJson(const Napi::CallbackInfo& info);
147
+ void OnJson(const Napi::CallbackInfo& info);
148
+ void OnceJson(const Napi::CallbackInfo& info);
149
+ void OffJson(const Napi::CallbackInfo& info);
150
+ Napi::Value SendJson(const Napi::CallbackInfo& info);
151
+ Napi::Value BroadcastJson(const Napi::CallbackInfo& info);
152
+ void OnJsonImpl(const Napi::CallbackInfo& info, bool once);
153
+
154
+ // ---- file transfer ----
155
+ void EnableFileTransfer(const Napi::CallbackInfo& info);
156
+ void OnFileOffer(const Napi::CallbackInfo& info);
157
+ void OnFileProgress(const Napi::CallbackInfo& info);
158
+ void OnFileComplete(const Napi::CallbackInfo& info);
159
+ Napi::Value SendFile(const Napi::CallbackInfo& info);
160
+ Napi::Value SendDirectory(const Napi::CallbackInfo& info);
161
+ Napi::Value AcceptFile(const Napi::CallbackInfo& info);
162
+ Napi::Value RejectFile(const Napi::CallbackInfo& info);
163
+ Napi::Value CancelFile(const Napi::CallbackInfo& info);
164
+ Napi::Value PauseFile(const Napi::CallbackInfo& info);
165
+ Napi::Value ResumeFile(const Napi::CallbackInfo& info);
166
+
167
+ // ---- ping / reconnect ----
168
+ void EnablePing(const Napi::CallbackInfo& info);
169
+ Napi::Value GetPeerRttMs(const Napi::CallbackInfo& info);
170
+ void EnableReconnect(const Napi::CallbackInfo& info);
171
+ Napi::Value AddReconnect(const Napi::CallbackInfo& info);
172
+ Napi::Value RemoveReconnect(const Napi::CallbackInfo& info);
173
+ };
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Construction / lifecycle
177
+ // ---------------------------------------------------------------------------
178
+
179
+ RatsNode::RatsNode(const Napi::CallbackInfo& info)
180
+ : Napi::ObjectWrap<RatsNode>(info) {
181
+ Napi::Env env = info.Env();
182
+
183
+ // Two construction forms:
184
+ // new RatsNode(port) -> rats_create(port)
185
+ // new RatsNode({ ...config }) -> rats_create_config(&cfg)
186
+ if (info.Length() >= 1 && info[0].IsObject() && !info[0].IsBuffer()) {
187
+ Napi::Object cfg = info[0].As<Napi::Object>();
188
+ rats_config_t c = rats_config_default();
189
+
190
+ // Hold string storage alive until rats_create_config() returns (the
191
+ // struct borrows the pointers only for the duration of the call).
192
+ std::string bind_addr, data_dir, protocol;
193
+
194
+ if (cfg.Has("listenPort"))
195
+ c.listen_port = static_cast<uint16_t>(cfg.Get("listenPort").As<Napi::Number>().Uint32Value());
196
+ if (cfg.Has("enableListen"))
197
+ c.enable_listen = cfg.Get("enableListen").As<Napi::Boolean>().Value() ? 1 : 0;
198
+ if (cfg.Has("bindAddress") && cfg.Get("bindAddress").IsString()) {
199
+ bind_addr = cfg.Get("bindAddress").As<Napi::String>().Utf8Value();
200
+ c.bind_address = bind_addr.c_str();
780
201
  }
781
- if (info.Length() > 1 && info[1].IsNumber()) {
782
- port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
202
+ if (cfg.Has("security"))
203
+ c.security = static_cast<rats_security_t>(cfg.Get("security").As<Napi::Number>().Int32Value());
204
+ if (cfg.Has("dataDir") && cfg.Get("dataDir").IsString()) {
205
+ data_dir = cfg.Get("dataDir").As<Napi::String>().Utf8Value();
206
+ c.data_dir = data_dir.c_str();
783
207
  }
784
- if (info.Length() > 2 && info[2].IsNumber()) {
785
- timeout_ms = info[2].As<Napi::Number>().Int32Value();
208
+ if (cfg.Has("protocol") && cfg.Get("protocol").IsString()) {
209
+ protocol = cfg.Get("protocol").As<Napi::String>().Utf8Value();
210
+ c.protocol = protocol.c_str();
786
211
  }
787
-
788
- char* address = rats_discover_public_address(client_, server, port, timeout_ms);
789
- if (!address) return env.Null();
790
-
791
- Napi::String result = Napi::String::New(env, address);
792
- rats_string_free(address);
793
- return result;
794
- }
795
-
796
- void AddRemoteIceCandidate(const Napi::CallbackInfo& info) {
797
- if (info.Length() < 1 || !info[0].IsString()) {
798
- Napi::TypeError::New(info.Env(), "Expected candidate_sdp (string)").ThrowAsJavaScriptException();
212
+ if (cfg.Has("maxPeers"))
213
+ c.max_peers = static_cast<size_t>(cfg.Get("maxPeers").As<Napi::Number>().Int64Value());
214
+
215
+ // Transports. Both are on by default and share one port; preferredTransport
216
+ // decides which a dial tries first (UDP), transportFallbackMs how long
217
+ // before the other is raced alongside it.
218
+ if (cfg.Has("enableTcp"))
219
+ c.enable_tcp = cfg.Get("enableTcp").As<Napi::Boolean>().Value() ? 1 : 0;
220
+ if (cfg.Has("enableUdp"))
221
+ c.enable_udp = cfg.Get("enableUdp").As<Napi::Boolean>().Value() ? 1 : 0;
222
+ if (cfg.Has("preferredTransport"))
223
+ c.preferred_transport = static_cast<rats_transport_t>(
224
+ cfg.Get("preferredTransport").As<Napi::Number>().Int32Value());
225
+ if (cfg.Has("transportFallbackMs"))
226
+ c.transport_fallback_ms =
227
+ cfg.Get("transportFallbackMs").As<Napi::Number>().Uint32Value();
228
+
229
+ node_ = rats_create_config(&c);
230
+ } else {
231
+ int port = 0;
232
+ if (info.Length() >= 1 && info[0].IsNumber()) {
233
+ port = info[0].As<Napi::Number>().Int32Value();
234
+ if (port < 0 || port > 65535) {
235
+ Napi::RangeError::New(env, "Port number must be between 0 and 65535")
236
+ .ThrowAsJavaScriptException();
237
+ return;
238
+ }
239
+ } else if (info.Length() >= 1) {
240
+ Napi::TypeError::New(env, "Expected a port number or a config object")
241
+ .ThrowAsJavaScriptException();
799
242
  return;
800
243
  }
801
-
802
- std::string candidate_sdp = info[0].As<Napi::String>().Utf8Value();
803
- rats_add_remote_ice_candidate(client_, candidate_sdp.c_str());
804
- }
805
-
806
- void EndOfRemoteIceCandidates(const Napi::CallbackInfo& info) {
807
- rats_end_of_remote_ice_candidates(client_);
808
- }
809
-
810
- void StartIceChecks(const Napi::CallbackInfo& info) {
811
- rats_start_ice_checks(client_);
812
- }
813
-
814
- Napi::Value GetIceConnectionState(const Napi::CallbackInfo& info) {
815
- Napi::Env env = info.Env();
816
- rats_ice_connection_state_t state = rats_get_ice_connection_state(client_);
817
- return Napi::Number::New(env, static_cast<int>(state));
818
- }
819
-
820
- Napi::Value GetIceGatheringState(const Napi::CallbackInfo& info) {
821
- Napi::Env env = info.Env();
822
- rats_ice_gathering_state_t state = rats_get_ice_gathering_state(client_);
823
- return Napi::Number::New(env, static_cast<int>(state));
824
- }
825
-
826
- Napi::Value IsIceConnected(const Napi::CallbackInfo& info) {
827
- Napi::Env env = info.Env();
828
- int connected = rats_is_ice_connected(client_);
829
- return Napi::Boolean::New(env, connected != 0);
830
- }
831
-
832
- Napi::Value GetIceSelectedPair(const Napi::CallbackInfo& info) {
833
- Napi::Env env = info.Env();
834
- char* pair_json = rats_get_ice_selected_pair_json(client_);
835
- if (!pair_json) return env.Null();
836
-
837
- Napi::String result = Napi::String::New(env, pair_json);
838
- rats_string_free(pair_json);
839
- return result;
840
- }
841
-
842
- void CloseIce(const Napi::CallbackInfo& info) {
843
- rats_close_ice(client_);
844
- }
845
-
846
- void RestartIce(const Napi::CallbackInfo& info) {
847
- rats_restart_ice(client_);
848
- }
849
-
850
- // GossipSub methods
851
- Napi::Value IsGossipsubAvailable(const Napi::CallbackInfo& info) {
852
- Napi::Env env = info.Env();
853
- int available = rats_is_gossipsub_available(client_);
854
- return Napi::Boolean::New(env, available != 0);
855
- }
856
-
857
- Napi::Value IsGossipsubRunning(const Napi::CallbackInfo& info) {
858
- Napi::Env env = info.Env();
859
- int running = rats_is_gossipsub_running(client_);
860
- return Napi::Boolean::New(env, running != 0);
861
- }
862
-
863
- Napi::Value SubscribeToTopic(const Napi::CallbackInfo& info) {
864
- Napi::Env env = info.Env();
865
-
866
- if (info.Length() < 1 || !info[0].IsString()) {
867
- Napi::TypeError::New(env, "Expected topic (string)").ThrowAsJavaScriptException();
868
- return env.Null();
869
- }
870
-
871
- std::string topic = info[0].As<Napi::String>().Utf8Value();
872
- rats_error_t result = rats_subscribe_to_topic(client_, topic.c_str());
873
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
244
+ node_ = rats_create(static_cast<uint16_t>(port));
874
245
  }
875
-
876
- Napi::Value UnsubscribeFromTopic(const Napi::CallbackInfo& info) {
877
- Napi::Env env = info.Env();
878
-
879
- if (info.Length() < 1 || !info[0].IsString()) {
880
- Napi::TypeError::New(env, "Expected topic (string)").ThrowAsJavaScriptException();
881
- return env.Null();
882
- }
883
-
884
- std::string topic = info[0].As<Napi::String>().Utf8Value();
885
- rats_error_t result = rats_unsubscribe_from_topic(client_, topic.c_str());
886
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
887
- }
888
-
889
- Napi::Value IsSubscribedToTopic(const Napi::CallbackInfo& info) {
890
- Napi::Env env = info.Env();
891
-
892
- if (info.Length() < 1 || !info[0].IsString()) {
893
- Napi::TypeError::New(env, "Expected topic (string)").ThrowAsJavaScriptException();
894
- return env.Null();
895
- }
896
-
897
- std::string topic = info[0].As<Napi::String>().Utf8Value();
898
- int subscribed = rats_is_subscribed_to_topic(client_, topic.c_str());
899
- return Napi::Boolean::New(env, subscribed != 0);
900
- }
901
-
902
- Napi::Value GetSubscribedTopics(const Napi::CallbackInfo& info) {
903
- Napi::Env env = info.Env();
904
- int count = 0;
905
- char** topics = rats_get_subscribed_topics(client_, &count);
906
-
907
- if (!topics || count == 0) {
908
- return Napi::Array::New(env, 0);
909
- }
910
-
911
- Napi::Array result = Napi::Array::New(env, count);
912
- for (int i = 0; i < count; i++) {
913
- result[i] = Napi::String::New(env, topics[i]);
914
- rats_string_free(topics[i]);
915
- }
916
- free(topics);
917
-
918
- return result;
246
+
247
+ if (!node_) {
248
+ Napi::Error::New(env, "Failed to create RatsNode").ThrowAsJavaScriptException();
249
+ return;
919
250
  }
920
-
921
- Napi::Value PublishToTopic(const Napi::CallbackInfo& info) {
922
- Napi::Env env = info.Env();
923
-
924
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
925
- Napi::TypeError::New(env, "Expected topic (string) and message (string)").ThrowAsJavaScriptException();
926
- return env.Null();
927
- }
928
-
929
- std::string topic = info[0].As<Napi::String>().Utf8Value();
930
- std::string message = info[1].As<Napi::String>().Utf8Value();
931
-
932
- rats_error_t result = rats_publish_to_topic(client_, topic.c_str(), message.c_str());
933
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
251
+ }
252
+
253
+ RatsNode::~RatsNode() {
254
+ Teardown();
255
+ }
256
+
257
+ // Release all TSFNs first so no JS callback can be invoked during/after
258
+ // destruction, then destroy the native node. Idempotent.
259
+ void RatsNode::Teardown() {
260
+ if (on_connected_) on_connected_->release();
261
+ if (on_disconnected_) on_disconnected_->release();
262
+ if (on_file_offer_) on_file_offer_->release();
263
+ if (on_file_progress_) on_file_progress_->release();
264
+ if (on_file_complete_) on_file_complete_->release();
265
+ for (auto& h : handlers_) h->release();
266
+
267
+ if (node_) {
268
+ rats_destroy(node_);
269
+ node_ = nullptr;
934
270
  }
935
-
936
- Napi::Value PublishJsonToTopic(const Napi::CallbackInfo& info) {
937
- Napi::Env env = info.Env();
938
-
939
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
940
- Napi::TypeError::New(env, "Expected topic (string) and json_str (string)").ThrowAsJavaScriptException();
941
- return env.Null();
942
- }
943
-
944
- std::string topic = info[0].As<Napi::String>().Utf8Value();
945
- std::string json_str = info[1].As<Napi::String>().Utf8Value();
946
-
947
- rats_error_t result = rats_publish_json_to_topic(client_, topic.c_str(), json_str.c_str());
948
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
271
+ }
272
+
273
+ void RatsNode::Start(const Napi::CallbackInfo& info) {
274
+ RATS_REQUIRE_NODE();
275
+ throw_on_error(info.Env(), rats_start(node_));
276
+ }
277
+
278
+ void RatsNode::Stop(const Napi::CallbackInfo& info) {
279
+ if (node_) rats_stop(node_);
280
+ }
281
+
282
+ // Explicit release. After this the instance is inert; further calls are no-ops
283
+ // or throw, and the GC has nothing left to free.
284
+ void RatsNode::Destroy(const Napi::CallbackInfo& info) {
285
+ Teardown();
286
+ }
287
+
288
+ Napi::Value RatsNode::GetListenPort(const Napi::CallbackInfo& info) {
289
+ RATS_REQUIRE_NODE(info.Env().Undefined());
290
+ return Napi::Number::New(info.Env(), rats_listen_port(node_));
291
+ }
292
+
293
+ Napi::Value RatsNode::GetLocalId(const Napi::CallbackInfo& info) {
294
+ RATS_REQUIRE_NODE(info.Env().Undefined());
295
+ Napi::Env env = info.Env();
296
+ char* id = rats_local_id(node_);
297
+ if (!id) return env.Null();
298
+ Napi::String result = Napi::String::New(env, id);
299
+ rats_string_free(id);
300
+ return result;
301
+ }
302
+
303
+ Napi::Value RatsNode::GetProtocol(const Napi::CallbackInfo& info) {
304
+ RATS_REQUIRE_NODE(info.Env().Undefined());
305
+ Napi::Env env = info.Env();
306
+ char* s = rats_protocol(node_);
307
+ if (!s) return env.Null();
308
+ Napi::String result = Napi::String::New(env, s);
309
+ rats_string_free(s);
310
+ return result;
311
+ }
312
+
313
+ // Transports actually running, as a RATS_TRANSPORT_MASK_* bitmask. May be
314
+ // narrower than the config asked for; 0 before start() and after stop().
315
+ Napi::Value RatsNode::GetTransports(const Napi::CallbackInfo& info) {
316
+ RATS_REQUIRE_NODE(info.Env().Undefined());
317
+ return Napi::Number::New(info.Env(), rats_transports(node_));
318
+ }
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // Connections
322
+ // ---------------------------------------------------------------------------
323
+
324
+ Napi::Value RatsNode::Connect(const Napi::CallbackInfo& info) {
325
+ RATS_REQUIRE_NODE(info.Env().Undefined());
326
+ Napi::Env env = info.Env();
327
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
328
+ Napi::TypeError::New(env, "Expected host (string) and port (number)")
329
+ .ThrowAsJavaScriptException();
330
+ return env.Undefined();
949
331
  }
950
-
951
- Napi::Value GetTopicPeers(const Napi::CallbackInfo& info) {
952
- Napi::Env env = info.Env();
953
-
954
- if (info.Length() < 1 || !info[0].IsString()) {
955
- Napi::TypeError::New(env, "Expected topic (string)").ThrowAsJavaScriptException();
956
- return env.Null();
957
- }
958
-
959
- std::string topic = info[0].As<Napi::String>().Utf8Value();
960
- int count = 0;
961
- char** peers = rats_get_topic_peers(client_, topic.c_str(), &count);
962
-
963
- if (!peers || count == 0) {
964
- return Napi::Array::New(env, 0);
965
- }
966
-
967
- Napi::Array result = Napi::Array::New(env, count);
968
- for (int i = 0; i < count; i++) {
969
- result[i] = Napi::String::New(env, peers[i]);
970
- rats_string_free(peers[i]);
332
+ std::string host = info[0].As<Napi::String>().Utf8Value();
333
+ uint16_t port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
334
+ throw_on_error(env, rats_connect(node_, host.c_str(), port));
335
+ return env.Undefined();
336
+ }
337
+
338
+ Napi::Value RatsNode::GetPeerCount(const Napi::CallbackInfo& info) {
339
+ RATS_REQUIRE_NODE(info.Env().Undefined());
340
+ return Napi::Number::New(info.Env(), static_cast<double>(rats_peer_count(node_)));
341
+ }
342
+
343
+ Napi::Value RatsNode::GetPeerIds(const Napi::CallbackInfo& info) {
344
+ RATS_REQUIRE_NODE(info.Env().Undefined());
345
+ Napi::Env env = info.Env();
346
+ size_t count = 0;
347
+ char** ids = rats_peer_ids(node_, &count);
348
+ Napi::Array result = Napi::Array::New(env, count);
349
+ if (ids) {
350
+ for (size_t i = 0; i < count; i++) {
351
+ result[static_cast<uint32_t>(i)] = Napi::String::New(env, ids[i]);
971
352
  }
972
- free(peers);
973
-
974
- return result;
975
- }
976
-
977
- Napi::Value GetGossipsubStatistics(const Napi::CallbackInfo& info) {
978
- Napi::Env env = info.Env();
979
- char* stats_json = rats_get_gossipsub_statistics_json(client_);
980
- if (!stats_json) return env.Null();
981
-
982
- Napi::String result = Napi::String::New(env, stats_json);
983
- rats_string_free(stats_json);
984
- return result;
353
+ rats_free_peer_ids(ids, count);
985
354
  }
986
-
987
- // File Transfer methods
988
- Napi::Value SendFile(const Napi::CallbackInfo& info) {
989
- Napi::Env env = info.Env();
990
-
991
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
992
- Napi::TypeError::New(env, "Expected peer_id (string) and file_path (string)").ThrowAsJavaScriptException();
993
- return env.Null();
994
- }
995
-
996
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
997
- std::string file_path = info[1].As<Napi::String>().Utf8Value();
998
-
999
- std::string remote_name;
1000
- const char* remote_filename = nullptr;
1001
- if (info.Length() > 2 && info[2].IsString()) {
1002
- remote_name = info[2].As<Napi::String>().Utf8Value();
1003
- remote_filename = remote_name.c_str();
1004
- }
355
+ return result;
356
+ }
1005
357
 
1006
- char* transfer_id = rats_send_file(client_, peer_id.c_str(), file_path.c_str(), remote_filename);
1007
- if (!transfer_id) return env.Null();
1008
-
1009
- Napi::String result = Napi::String::New(env, transfer_id);
1010
- rats_string_free(transfer_id);
1011
- return result;
358
+ void RatsNode::SetMaxPeers(const Napi::CallbackInfo& info) {
359
+ RATS_REQUIRE_NODE();
360
+ Napi::Env env = info.Env();
361
+ if (info.Length() < 1 || !info[0].IsNumber()) {
362
+ Napi::TypeError::New(env, "Expected maxPeers (number)").ThrowAsJavaScriptException();
363
+ return;
1012
364
  }
1013
-
1014
- Napi::Value SendDirectory(const Napi::CallbackInfo& info) {
1015
- Napi::Env env = info.Env();
1016
-
1017
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
1018
- Napi::TypeError::New(env, "Expected peer_id (string) and directory_path (string)").ThrowAsJavaScriptException();
1019
- return env.Null();
1020
- }
1021
-
1022
- std::string peer_id = info[0].As<Napi::String>().Utf8Value();
1023
- std::string directory_path = info[1].As<Napi::String>().Utf8Value();
1024
-
1025
- std::string remote_name;
1026
- const char* remote_directory_name = nullptr;
1027
- if (info.Length() > 2 && info[2].IsString()) {
1028
- remote_name = info[2].As<Napi::String>().Utf8Value();
1029
- remote_directory_name = remote_name.c_str();
1030
- }
365
+ rats_set_max_peers(node_, static_cast<size_t>(info[0].As<Napi::Number>().Int64Value()));
366
+ }
367
+
368
+ Napi::Value RatsNode::GetMaxPeers(const Napi::CallbackInfo& info) {
369
+ RATS_REQUIRE_NODE(info.Env().Undefined());
370
+ return Napi::Number::New(info.Env(), static_cast<double>(rats_max_peers(node_)));
371
+ }
1031
372
 
1032
- // Directory transfers are always recursive; the trailing flag is ignored.
1033
- char* transfer_id = rats_send_directory(client_, peer_id.c_str(), directory_path.c_str(), remote_directory_name, 1);
1034
- if (!transfer_id) return env.Null();
1035
-
1036
- Napi::String result = Napi::String::New(env, transfer_id);
1037
- rats_string_free(transfer_id);
1038
- return result;
373
+ // Which transport a connected peer's link runs on; null if not connected.
374
+ Napi::Value RatsNode::GetPeerTransport(const Napi::CallbackInfo& info) {
375
+ RATS_REQUIRE_NODE(info.Env().Undefined());
376
+ Napi::Env env = info.Env();
377
+ if (info.Length() < 1 || !info[0].IsString()) {
378
+ Napi::TypeError::New(env, "Expected peerId (string)").ThrowAsJavaScriptException();
379
+ return env.Undefined();
1039
380
  }
1040
-
1041
- Napi::Value AcceptFileTransfer(const Napi::CallbackInfo& info) {
1042
- Napi::Env env = info.Env();
1043
-
1044
- if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
1045
- Napi::TypeError::New(env, "Expected transfer_id (string) and local_path (string)").ThrowAsJavaScriptException();
1046
- return env.Null();
1047
- }
1048
-
1049
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1050
- std::string local_path = info[1].As<Napi::String>().Utf8Value();
1051
-
1052
- rats_error_t result = rats_accept_file_transfer(client_, transfer_id.c_str(), local_path.c_str());
1053
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
381
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
382
+ int t = rats_peer_transport(node_, peer.c_str());
383
+ if (t < 0) return env.Null();
384
+ return Napi::Number::New(env, t);
385
+ }
386
+
387
+ // Transports a connected peer advertised, as a bitmask; null if not connected.
388
+ // 0 means the peer did not say (an older build) — "no information", not "none".
389
+ Napi::Value RatsNode::GetPeerTransports(const Napi::CallbackInfo& info) {
390
+ RATS_REQUIRE_NODE(info.Env().Undefined());
391
+ Napi::Env env = info.Env();
392
+ if (info.Length() < 1 || !info[0].IsString()) {
393
+ Napi::TypeError::New(env, "Expected peerId (string)").ThrowAsJavaScriptException();
394
+ return env.Undefined();
1054
395
  }
1055
-
1056
- Napi::Value RejectFileTransfer(const Napi::CallbackInfo& info) {
1057
- Napi::Env env = info.Env();
1058
-
1059
- if (info.Length() < 1 || !info[0].IsString()) {
1060
- Napi::TypeError::New(env, "Expected transfer_id (string)").ThrowAsJavaScriptException();
1061
- return env.Null();
1062
- }
1063
-
1064
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1065
-
1066
- std::string reason_str;
1067
- const char* reason = nullptr;
1068
- if (info.Length() > 1 && info[1].IsString()) {
1069
- reason_str = info[1].As<Napi::String>().Utf8Value();
1070
- reason = reason_str.c_str();
1071
- }
396
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
397
+ int t = rats_peer_transports(node_, peer.c_str());
398
+ if (t < 0) return env.Null();
399
+ return Napi::Number::New(env, t);
400
+ }
401
+
402
+ // ---------------------------------------------------------------------------
403
+ // Raw channel messaging
404
+ // ---------------------------------------------------------------------------
405
+
406
+ // Coerce a JS string or Buffer argument into a contiguous byte vector.
407
+ static bool to_bytes(Napi::Env env, const Napi::Value& v, std::vector<uint8_t>& out) {
408
+ if (v.IsBuffer()) {
409
+ Napi::Buffer<uint8_t> buf = v.As<Napi::Buffer<uint8_t>>();
410
+ out.assign(buf.Data(), buf.Data() + buf.Length());
411
+ return true;
412
+ }
413
+ if (v.IsString()) {
414
+ std::string s = v.As<Napi::String>().Utf8Value();
415
+ out.assign(s.begin(), s.end());
416
+ return true;
417
+ }
418
+ Napi::TypeError::New(env, "Expected data (string or Buffer)").ThrowAsJavaScriptException();
419
+ return false;
420
+ }
1072
421
 
1073
- rats_error_t result = rats_reject_file_transfer(client_, transfer_id.c_str(), reason);
1074
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
422
+ Napi::Value RatsNode::Send(const Napi::CallbackInfo& info) {
423
+ RATS_REQUIRE_NODE(info.Env().Undefined());
424
+ Napi::Env env = info.Env();
425
+ if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString()) {
426
+ Napi::TypeError::New(env, "Expected peerId (string), channel (string), data")
427
+ .ThrowAsJavaScriptException();
428
+ return env.Undefined();
1075
429
  }
1076
-
1077
- Napi::Value CancelFileTransfer(const Napi::CallbackInfo& info) {
1078
- Napi::Env env = info.Env();
1079
-
1080
- if (info.Length() < 1 || !info[0].IsString()) {
1081
- Napi::TypeError::New(env, "Expected transfer_id (string)").ThrowAsJavaScriptException();
1082
- return env.Null();
1083
- }
1084
-
1085
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1086
-
1087
- rats_error_t result = rats_cancel_file_transfer(client_, transfer_id.c_str());
1088
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
430
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
431
+ std::string channel = info[1].As<Napi::String>().Utf8Value();
432
+ std::vector<uint8_t> data;
433
+ if (!to_bytes(env, info[2], data)) return env.Undefined();
434
+ throw_on_error(env, rats_send(node_, peer.c_str(), channel.c_str(), data.data(), data.size()));
435
+ return env.Undefined();
436
+ }
437
+
438
+ Napi::Value RatsNode::Broadcast(const Napi::CallbackInfo& info) {
439
+ RATS_REQUIRE_NODE(info.Env().Undefined());
440
+ Napi::Env env = info.Env();
441
+ if (info.Length() < 2 || !info[0].IsString()) {
442
+ Napi::TypeError::New(env, "Expected channel (string) and data")
443
+ .ThrowAsJavaScriptException();
444
+ return env.Undefined();
1089
445
  }
1090
-
1091
- Napi::Value PauseFileTransfer(const Napi::CallbackInfo& info) {
1092
- Napi::Env env = info.Env();
1093
-
1094
- if (info.Length() < 1 || !info[0].IsString()) {
1095
- Napi::TypeError::New(env, "Expected transfer_id (string)").ThrowAsJavaScriptException();
1096
- return env.Null();
1097
- }
1098
-
1099
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1100
-
1101
- rats_error_t result = rats_pause_file_transfer(client_, transfer_id.c_str());
1102
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
446
+ std::string channel = info[0].As<Napi::String>().Utf8Value();
447
+ std::vector<uint8_t> data;
448
+ if (!to_bytes(env, info[1], data)) return env.Undefined();
449
+ throw_on_error(env, rats_broadcast(node_, channel.c_str(), data.data(), data.size()));
450
+ return env.Undefined();
451
+ }
452
+
453
+ // rats_message_cb(user, peer_id_hex, data, len) -> JS (peerId, Buffer)
454
+ void RatsNode::On(const Napi::CallbackInfo& info) {
455
+ RATS_REQUIRE_NODE();
456
+ Napi::Env env = info.Env();
457
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsFunction()) {
458
+ Napi::TypeError::New(env, "Expected channel (string) and callback (function)")
459
+ .ThrowAsJavaScriptException();
460
+ return;
461
+ }
462
+ std::string channel = info[0].As<Napi::String>().Utf8Value();
463
+ CbContext* ctx = new_handler();
464
+ ctx->init(env, info[1].As<Napi::Function>(), "on_message");
465
+
466
+ auto trampoline = [](void* user, const char* peer_id, const void* data, size_t len) {
467
+ auto* c = static_cast<CbContext*>(user);
468
+ std::string peer = peer_id ? peer_id : "";
469
+ std::vector<uint8_t> bytes(static_cast<const uint8_t*>(data),
470
+ static_cast<const uint8_t*>(data) + len);
471
+ c->tsfn.BlockingCall([peer, bytes](Napi::Env env, Napi::Function js) {
472
+ js.Call({Napi::String::New(env, peer),
473
+ Napi::Buffer<uint8_t>::Copy(env, bytes.data(), bytes.size())});
474
+ });
475
+ };
476
+ throw_on_error(env, rats_on(node_, channel.c_str(), trampoline, ctx));
477
+ }
478
+
479
+ // ---------------------------------------------------------------------------
480
+ // Peer events
481
+ // ---------------------------------------------------------------------------
482
+
483
+ void RatsNode::OnPeerConnected(const Napi::CallbackInfo& info) {
484
+ RATS_REQUIRE_NODE();
485
+ Napi::Env env = info.Env();
486
+ if (info.Length() < 1 || !info[0].IsFunction()) {
487
+ Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
488
+ return;
489
+ }
490
+ on_connected_ = std::make_unique<CbContext>();
491
+ on_connected_->init(env, info[0].As<Napi::Function>(), "on_peer_connected");
492
+ auto trampoline = [](void* user, const char* peer_id) {
493
+ auto* c = static_cast<CbContext*>(user);
494
+ std::string peer = peer_id ? peer_id : "";
495
+ c->tsfn.BlockingCall([peer](Napi::Env env, Napi::Function js) {
496
+ js.Call({Napi::String::New(env, peer)});
497
+ });
498
+ };
499
+ throw_on_error(env, rats_on_peer_connected(node_, trampoline, on_connected_.get()));
500
+ }
501
+
502
+ void RatsNode::OnPeerDisconnected(const Napi::CallbackInfo& info) {
503
+ RATS_REQUIRE_NODE();
504
+ Napi::Env env = info.Env();
505
+ if (info.Length() < 1 || !info[0].IsFunction()) {
506
+ Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
507
+ return;
508
+ }
509
+ on_disconnected_ = std::make_unique<CbContext>();
510
+ on_disconnected_->init(env, info[0].As<Napi::Function>(), "on_peer_disconnected");
511
+ auto trampoline = [](void* user, const char* peer_id) {
512
+ auto* c = static_cast<CbContext*>(user);
513
+ std::string peer = peer_id ? peer_id : "";
514
+ c->tsfn.BlockingCall([peer](Napi::Env env, Napi::Function js) {
515
+ js.Call({Napi::String::New(env, peer)});
516
+ });
517
+ };
518
+ throw_on_error(env, rats_on_peer_disconnected(node_, trampoline, on_disconnected_.get()));
519
+ }
520
+
521
+ // ---------------------------------------------------------------------------
522
+ // Discovery / NAT
523
+ // ---------------------------------------------------------------------------
524
+
525
+ void RatsNode::EnableDht(const Napi::CallbackInfo& info) {
526
+ RATS_REQUIRE_NODE();
527
+ Napi::Env env = info.Env();
528
+ uint16_t dht_port = 0;
529
+ std::string key;
530
+ const char* key_ptr = nullptr;
531
+ if (info.Length() >= 1 && info[0].IsNumber())
532
+ dht_port = static_cast<uint16_t>(info[0].As<Napi::Number>().Uint32Value());
533
+ if (info.Length() >= 2 && info[1].IsString()) {
534
+ key = info[1].As<Napi::String>().Utf8Value();
535
+ key_ptr = key.c_str();
536
+ }
537
+ throw_on_error(env, rats_enable_dht(node_, dht_port, key_ptr));
538
+ }
539
+
540
+ void RatsNode::EnableMdns(const Napi::CallbackInfo& info) {
541
+ RATS_REQUIRE_NODE();
542
+ throw_on_error(info.Env(), rats_enable_mdns(node_));
543
+ }
544
+
545
+ void RatsNode::EnablePortMapping(const Napi::CallbackInfo& info) {
546
+ RATS_REQUIRE_NODE();
547
+ int upnp = 1, natpmp = 1;
548
+ if (info.Length() >= 1 && info[0].IsBoolean()) upnp = info[0].As<Napi::Boolean>().Value() ? 1 : 0;
549
+ if (info.Length() >= 2 && info[1].IsBoolean()) natpmp = info[1].As<Napi::Boolean>().Value() ? 1 : 0;
550
+ throw_on_error(info.Env(), rats_enable_port_mapping(node_, upnp, natpmp));
551
+ }
552
+
553
+ // Hole punching. serveAsRelay (default true) also carries other peers'
554
+ // rendezvous — a mesh in which nobody relays cannot punch at all.
555
+ void RatsNode::EnableHolePunch(const Napi::CallbackInfo& info) {
556
+ RATS_REQUIRE_NODE();
557
+ int relay = 1;
558
+ if (info.Length() >= 1 && info[0].IsBoolean()) relay = info[0].As<Napi::Boolean>().Value() ? 1 : 0;
559
+ throw_on_error(info.Env(), rats_enable_hole_punch(node_, relay));
560
+ }
561
+
562
+ // Start a punch to a peer. Non-blocking: success arrives as onPeerConnected.
563
+ Napi::Value RatsNode::PunchPeer(const Napi::CallbackInfo& info) {
564
+ RATS_REQUIRE_NODE(info.Env().Undefined());
565
+ Napi::Env env = info.Env();
566
+ if (info.Length() < 1 || !info[0].IsString()) {
567
+ Napi::TypeError::New(env, "Expected peerId (string)").ThrowAsJavaScriptException();
568
+ return env.Undefined();
1103
569
  }
1104
-
1105
- Napi::Value ResumeFileTransfer(const Napi::CallbackInfo& info) {
1106
- Napi::Env env = info.Env();
1107
-
1108
- if (info.Length() < 1 || !info[0].IsString()) {
1109
- Napi::TypeError::New(env, "Expected transfer_id (string)").ThrowAsJavaScriptException();
1110
- return env.Null();
1111
- }
1112
-
1113
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1114
-
1115
- rats_error_t result = rats_resume_file_transfer(client_, transfer_id.c_str());
1116
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
570
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
571
+ throw_on_error(env, rats_punch_peer(node_, peer.c_str()));
572
+ return env.Undefined();
573
+ }
574
+
575
+ // Relaying. serveAsRelay (default false) also carries OTHER peers' connections,
576
+ // which spends real bandwidth — so it is opted into rather than assumed.
577
+ void RatsNode::EnableRelay(const Napi::CallbackInfo& info) {
578
+ RATS_REQUIRE_NODE();
579
+ int serve = 0;
580
+ if (info.Length() >= 1 && info[0].IsBoolean()) serve = info[0].As<Napi::Boolean>().Value() ? 1 : 0;
581
+ throw_on_error(info.Env(), rats_enable_relay(node_, serve));
582
+ }
583
+
584
+ // Reach a peer through a relay. Non-blocking: success arrives as onPeerConnected.
585
+ Napi::Value RatsNode::ConnectViaRelay(const Napi::CallbackInfo& info) {
586
+ RATS_REQUIRE_NODE(info.Env().Undefined());
587
+ Napi::Env env = info.Env();
588
+ if (info.Length() < 1 || !info[0].IsString()) {
589
+ Napi::TypeError::New(env, "Expected peerId (string)").ThrowAsJavaScriptException();
590
+ return env.Undefined();
1117
591
  }
1118
-
1119
- Napi::Value GetFileTransferProgress(const Napi::CallbackInfo& info) {
1120
- Napi::Env env = info.Env();
1121
-
1122
- if (info.Length() < 1 || !info[0].IsString()) {
1123
- Napi::TypeError::New(env, "Expected transfer_id (string)").ThrowAsJavaScriptException();
1124
- return env.Null();
1125
- }
1126
-
1127
- std::string transfer_id = info[0].As<Napi::String>().Utf8Value();
1128
-
1129
- char* progress_json = rats_get_file_transfer_progress_json(client_, transfer_id.c_str());
1130
- if (!progress_json) return env.Null();
1131
-
1132
- Napi::String result = Napi::String::New(env, progress_json);
1133
- rats_string_free(progress_json);
1134
- return result;
592
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
593
+ throw_on_error(env, rats_connect_via_relay(node_, peer.c_str()));
594
+ return env.Undefined();
595
+ }
596
+
597
+ // What the mesh has shown about this node's own NAT (a RATS_NAT_* value).
598
+ Napi::Value RatsNode::GetNatMapping(const Napi::CallbackInfo& info) {
599
+ RATS_REQUIRE_NODE(info.Env().Undefined());
600
+ return Napi::Number::New(info.Env(), rats_nat_mapping(node_));
601
+ }
602
+
603
+ // ---------------------------------------------------------------------------
604
+ // Pub/sub
605
+ // ---------------------------------------------------------------------------
606
+
607
+ void RatsNode::EnablePubsub(const Napi::CallbackInfo& info) {
608
+ RATS_REQUIRE_NODE();
609
+ throw_on_error(info.Env(), rats_enable_pubsub(node_));
610
+ }
611
+
612
+ // rats_topic_cb(user, peer_id_hex, topic, data, len) -> JS (peerId, topic, Buffer)
613
+ void RatsNode::Subscribe(const Napi::CallbackInfo& info) {
614
+ RATS_REQUIRE_NODE();
615
+ Napi::Env env = info.Env();
616
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsFunction()) {
617
+ Napi::TypeError::New(env, "Expected topic (string) and callback (function)")
618
+ .ThrowAsJavaScriptException();
619
+ return;
620
+ }
621
+ std::string topic = info[0].As<Napi::String>().Utf8Value();
622
+ CbContext* ctx = new_handler();
623
+ ctx->init(env, info[1].As<Napi::Function>(), "on_topic");
624
+ auto trampoline = [](void* user, const char* peer_id, const char* topic,
625
+ const void* data, size_t len) {
626
+ auto* c = static_cast<CbContext*>(user);
627
+ std::string peer = peer_id ? peer_id : "";
628
+ std::string t = topic ? topic : "";
629
+ std::vector<uint8_t> bytes(static_cast<const uint8_t*>(data),
630
+ static_cast<const uint8_t*>(data) + len);
631
+ c->tsfn.BlockingCall([peer, t, bytes](Napi::Env env, Napi::Function js) {
632
+ js.Call({Napi::String::New(env, peer),
633
+ Napi::String::New(env, t),
634
+ Napi::Buffer<uint8_t>::Copy(env, bytes.data(), bytes.size())});
635
+ });
636
+ };
637
+ throw_on_error(env, rats_subscribe(node_, topic.c_str(), trampoline, ctx));
638
+ }
639
+
640
+ void RatsNode::Unsubscribe(const Napi::CallbackInfo& info) {
641
+ RATS_REQUIRE_NODE();
642
+ Napi::Env env = info.Env();
643
+ if (info.Length() < 1 || !info[0].IsString()) {
644
+ Napi::TypeError::New(env, "Expected topic (string)").ThrowAsJavaScriptException();
645
+ return;
1135
646
  }
1136
-
1137
- Napi::Value GetFileTransferStatistics(const Napi::CallbackInfo& info) {
1138
- Napi::Env env = info.Env();
1139
- char* stats_json = rats_get_file_transfer_statistics_json(client_);
1140
- if (!stats_json) return env.Null();
1141
-
1142
- Napi::String result = Napi::String::New(env, stats_json);
1143
- rats_string_free(stats_json);
1144
- return result;
647
+ std::string topic = info[0].As<Napi::String>().Utf8Value();
648
+ throw_on_error(env, rats_unsubscribe(node_, topic.c_str()));
649
+ }
650
+
651
+ Napi::Value RatsNode::Publish(const Napi::CallbackInfo& info) {
652
+ RATS_REQUIRE_NODE(info.Env().Undefined());
653
+ Napi::Env env = info.Env();
654
+ if (info.Length() < 2 || !info[0].IsString()) {
655
+ Napi::TypeError::New(env, "Expected topic (string) and data")
656
+ .ThrowAsJavaScriptException();
657
+ return env.Undefined();
1145
658
  }
1146
-
1147
- // Address blocking
1148
- void AddIgnoredAddress(const Napi::CallbackInfo& info) {
1149
- if (info.Length() < 1 || !info[0].IsString()) {
1150
- Napi::TypeError::New(info.Env(), "Expected ip_address (string)").ThrowAsJavaScriptException();
1151
- return;
1152
- }
1153
-
1154
- std::string ip_address = info[0].As<Napi::String>().Utf8Value();
1155
- rats_add_ignored_address(client_, ip_address.c_str());
659
+ std::string topic = info[0].As<Napi::String>().Utf8Value();
660
+ std::vector<uint8_t> data;
661
+ if (!to_bytes(env, info[1], data)) return env.Undefined();
662
+ throw_on_error(env, rats_publish(node_, topic.c_str(), data.data(), data.size()));
663
+ return env.Undefined();
664
+ }
665
+
666
+ // ---------------------------------------------------------------------------
667
+ // Typed JSON
668
+ // ---------------------------------------------------------------------------
669
+
670
+ void RatsNode::EnableJson(const Napi::CallbackInfo& info) {
671
+ RATS_REQUIRE_NODE();
672
+ throw_on_error(info.Env(), rats_enable_json(node_));
673
+ }
674
+
675
+ void RatsNode::OnJsonImpl(const Napi::CallbackInfo& info, bool once) {
676
+ Napi::Env env = info.Env();
677
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsFunction()) {
678
+ Napi::TypeError::New(env, "Expected type (string) and callback (function)")
679
+ .ThrowAsJavaScriptException();
680
+ return;
681
+ }
682
+ std::string type = info[0].As<Napi::String>().Utf8Value();
683
+ CbContext* ctx = new_handler();
684
+ ctx->init(env, info[1].As<Napi::Function>(), "on_json");
685
+ auto trampoline = [](void* user, const char* peer_id, const char* json) {
686
+ auto* c = static_cast<CbContext*>(user);
687
+ std::string peer = peer_id ? peer_id : "";
688
+ std::string j = json ? json : "";
689
+ c->tsfn.BlockingCall([peer, j](Napi::Env env, Napi::Function js) {
690
+ js.Call({Napi::String::New(env, peer), Napi::String::New(env, j)});
691
+ });
692
+ };
693
+ if (once)
694
+ throw_on_error(env, rats_once_json(node_, type.c_str(), trampoline, ctx));
695
+ else
696
+ throw_on_error(env, rats_on_json(node_, type.c_str(), trampoline, ctx));
697
+ }
698
+
699
+ void RatsNode::OnJson(const Napi::CallbackInfo& info) { OnJsonImpl(info, false); }
700
+ void RatsNode::OnceJson(const Napi::CallbackInfo& info) { OnJsonImpl(info, true); }
701
+
702
+ void RatsNode::OffJson(const Napi::CallbackInfo& info) {
703
+ RATS_REQUIRE_NODE();
704
+ Napi::Env env = info.Env();
705
+ if (info.Length() < 1 || !info[0].IsString()) {
706
+ Napi::TypeError::New(env, "Expected type (string)").ThrowAsJavaScriptException();
707
+ return;
1156
708
  }
1157
-
1158
- // Callback methods
1159
- void OnConnection(const Napi::CallbackInfo& info) {
1160
- Napi::Env env = info.Env();
1161
-
1162
- if (info.Length() < 1 || !info[0].IsFunction()) {
1163
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1164
- return;
1165
- }
1166
-
1167
- auto callback_data = std::make_shared<CallbackData>(env);
1168
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1169
-
1170
- connection_callbacks[client_] = callback_data;
1171
- rats_set_connection_callback(client_, connection_callback_wrapper, client_);
709
+ std::string type = info[0].As<Napi::String>().Utf8Value();
710
+ throw_on_error(env, rats_off_json(node_, type.c_str()));
711
+ }
712
+
713
+ Napi::Value RatsNode::SendJson(const Napi::CallbackInfo& info) {
714
+ RATS_REQUIRE_NODE(info.Env().Undefined());
715
+ Napi::Env env = info.Env();
716
+ if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
717
+ Napi::TypeError::New(env, "Expected peerId (string), type (string), json (string)")
718
+ .ThrowAsJavaScriptException();
719
+ return env.Undefined();
1172
720
  }
1173
-
1174
- void OnString(const Napi::CallbackInfo& info) {
1175
- Napi::Env env = info.Env();
1176
-
1177
- if (info.Length() < 1 || !info[0].IsFunction()) {
1178
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1179
- return;
1180
- }
1181
-
1182
- auto callback_data = std::make_shared<CallbackData>(env);
1183
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1184
-
1185
- string_callbacks[client_] = callback_data;
1186
- rats_set_string_callback(client_, string_callback_wrapper, client_);
721
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
722
+ std::string type = info[1].As<Napi::String>().Utf8Value();
723
+ std::string json = info[2].As<Napi::String>().Utf8Value();
724
+ throw_on_error(env, rats_send_json(node_, peer.c_str(), type.c_str(), json.c_str()));
725
+ return env.Undefined();
726
+ }
727
+
728
+ Napi::Value RatsNode::BroadcastJson(const Napi::CallbackInfo& info) {
729
+ RATS_REQUIRE_NODE(info.Env().Undefined());
730
+ Napi::Env env = info.Env();
731
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
732
+ Napi::TypeError::New(env, "Expected type (string) and json (string)")
733
+ .ThrowAsJavaScriptException();
734
+ return env.Undefined();
1187
735
  }
1188
-
1189
- void OnBinary(const Napi::CallbackInfo& info) {
1190
- Napi::Env env = info.Env();
1191
-
1192
- if (info.Length() < 1 || !info[0].IsFunction()) {
1193
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1194
- return;
1195
- }
1196
-
1197
- auto callback_data = std::make_shared<CallbackData>(env);
1198
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1199
-
1200
- binary_callbacks[client_] = callback_data;
1201
- rats_set_binary_callback(client_, binary_callback_wrapper, client_);
736
+ std::string type = info[0].As<Napi::String>().Utf8Value();
737
+ std::string json = info[1].As<Napi::String>().Utf8Value();
738
+ throw_on_error(env, rats_broadcast_json(node_, type.c_str(), json.c_str()));
739
+ return env.Undefined();
740
+ }
741
+
742
+ // ---------------------------------------------------------------------------
743
+ // File transfer
744
+ // ---------------------------------------------------------------------------
745
+
746
+ void RatsNode::EnableFileTransfer(const Napi::CallbackInfo& info) {
747
+ RATS_REQUIRE_NODE();
748
+ Napi::Env env = info.Env();
749
+ std::string tmp;
750
+ const char* tmp_ptr = nullptr;
751
+ if (info.Length() >= 1 && info[0].IsString()) {
752
+ tmp = info[0].As<Napi::String>().Utf8Value();
753
+ tmp_ptr = tmp.c_str();
1202
754
  }
1203
-
1204
- void OnJson(const Napi::CallbackInfo& info) {
1205
- Napi::Env env = info.Env();
1206
-
1207
- if (info.Length() < 1 || !info[0].IsFunction()) {
1208
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1209
- return;
1210
- }
1211
-
1212
- auto callback_data = std::make_shared<CallbackData>(env);
1213
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1214
-
1215
- json_callbacks[client_] = callback_data;
1216
- rats_set_json_callback(client_, json_callback_wrapper, client_);
755
+ throw_on_error(env, rats_enable_file_transfer(node_, tmp_ptr));
756
+ }
757
+
758
+ // rats_file_offer_cb(user, peer_id, transfer_id, name, size, is_directory)
759
+ void RatsNode::OnFileOffer(const Napi::CallbackInfo& info) {
760
+ RATS_REQUIRE_NODE();
761
+ Napi::Env env = info.Env();
762
+ if (info.Length() < 1 || !info[0].IsFunction()) {
763
+ Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
764
+ return;
765
+ }
766
+ on_file_offer_ = std::make_unique<CbContext>();
767
+ on_file_offer_->init(env, info[0].As<Napi::Function>(), "on_file_offer");
768
+ auto trampoline = [](void* user, const char* peer_id, uint64_t transfer_id,
769
+ const char* name, uint64_t size, int is_directory) {
770
+ auto* c = static_cast<CbContext*>(user);
771
+ std::string peer = peer_id ? peer_id : "";
772
+ std::string n = name ? name : "";
773
+ bool isdir = is_directory != 0;
774
+ c->tsfn.BlockingCall([peer, transfer_id, n, size, isdir](Napi::Env env, Napi::Function js) {
775
+ js.Call({Napi::String::New(env, peer),
776
+ Napi::Number::New(env, static_cast<double>(transfer_id)),
777
+ Napi::String::New(env, n),
778
+ Napi::Number::New(env, static_cast<double>(size)),
779
+ Napi::Boolean::New(env, isdir)});
780
+ });
781
+ };
782
+ throw_on_error(env, rats_on_file_offer(node_, trampoline, on_file_offer_.get()));
783
+ }
784
+
785
+ // rats_file_progress_cb(user, transfer_id, peer_id, bytes_transferred, total_bytes, status)
786
+ void RatsNode::OnFileProgress(const Napi::CallbackInfo& info) {
787
+ RATS_REQUIRE_NODE();
788
+ Napi::Env env = info.Env();
789
+ if (info.Length() < 1 || !info[0].IsFunction()) {
790
+ Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
791
+ return;
792
+ }
793
+ on_file_progress_ = std::make_unique<CbContext>();
794
+ on_file_progress_->init(env, info[0].As<Napi::Function>(), "on_file_progress");
795
+ auto trampoline = [](void* user, uint64_t transfer_id, const char* peer_id,
796
+ uint64_t bytes_transferred, uint64_t total_bytes, int status) {
797
+ auto* c = static_cast<CbContext*>(user);
798
+ std::string peer = peer_id ? peer_id : "";
799
+ c->tsfn.BlockingCall([transfer_id, peer, bytes_transferred, total_bytes, status]
800
+ (Napi::Env env, Napi::Function js) {
801
+ js.Call({Napi::Number::New(env, static_cast<double>(transfer_id)),
802
+ Napi::String::New(env, peer),
803
+ Napi::Number::New(env, static_cast<double>(bytes_transferred)),
804
+ Napi::Number::New(env, static_cast<double>(total_bytes)),
805
+ Napi::Number::New(env, status)});
806
+ });
807
+ };
808
+ throw_on_error(env, rats_on_file_progress(node_, trampoline, on_file_progress_.get()));
809
+ }
810
+
811
+ // rats_file_complete_cb(user, transfer_id, success, path)
812
+ void RatsNode::OnFileComplete(const Napi::CallbackInfo& info) {
813
+ RATS_REQUIRE_NODE();
814
+ Napi::Env env = info.Env();
815
+ if (info.Length() < 1 || !info[0].IsFunction()) {
816
+ Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
817
+ return;
818
+ }
819
+ on_file_complete_ = std::make_unique<CbContext>();
820
+ on_file_complete_->init(env, info[0].As<Napi::Function>(), "on_file_complete");
821
+ auto trampoline = [](void* user, uint64_t transfer_id, int success, const char* path) {
822
+ auto* c = static_cast<CbContext*>(user);
823
+ std::string p = path ? path : "";
824
+ bool ok = success != 0;
825
+ c->tsfn.BlockingCall([transfer_id, ok, p](Napi::Env env, Napi::Function js) {
826
+ js.Call({Napi::Number::New(env, static_cast<double>(transfer_id)),
827
+ Napi::Boolean::New(env, ok),
828
+ Napi::String::New(env, p)});
829
+ });
830
+ };
831
+ throw_on_error(env, rats_on_file_complete(node_, trampoline, on_file_complete_.get()));
832
+ }
833
+
834
+ Napi::Value RatsNode::SendFile(const Napi::CallbackInfo& info) {
835
+ RATS_REQUIRE_NODE(info.Env().Undefined());
836
+ Napi::Env env = info.Env();
837
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
838
+ Napi::TypeError::New(env, "Expected peerId (string) and path (string)")
839
+ .ThrowAsJavaScriptException();
840
+ return env.Undefined();
1217
841
  }
1218
-
1219
- void OnDisconnect(const Napi::CallbackInfo& info) {
1220
- Napi::Env env = info.Env();
1221
-
1222
- if (info.Length() < 1 || !info[0].IsFunction()) {
1223
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1224
- return;
1225
- }
1226
-
1227
- auto callback_data = std::make_shared<CallbackData>(env);
1228
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1229
-
1230
- disconnect_callbacks[client_] = callback_data;
1231
- rats_set_disconnect_callback(client_, disconnect_callback_wrapper, client_);
842
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
843
+ std::string path = info[1].As<Napi::String>().Utf8Value();
844
+ uint64_t id = rats_send_file(node_, peer.c_str(), path.c_str());
845
+ return Napi::Number::New(env, static_cast<double>(id));
846
+ }
847
+
848
+ Napi::Value RatsNode::SendDirectory(const Napi::CallbackInfo& info) {
849
+ RATS_REQUIRE_NODE(info.Env().Undefined());
850
+ Napi::Env env = info.Env();
851
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsString()) {
852
+ Napi::TypeError::New(env, "Expected peerId (string) and dirPath (string)")
853
+ .ThrowAsJavaScriptException();
854
+ return env.Undefined();
1232
855
  }
1233
-
1234
- void OnFileProgress(const Napi::CallbackInfo& info) {
1235
- Napi::Env env = info.Env();
1236
-
1237
- if (info.Length() < 1 || !info[0].IsFunction()) {
1238
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1239
- return;
1240
- }
1241
-
1242
- auto callback_data = std::make_shared<CallbackData>(env);
1243
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
1244
-
1245
- file_progress_callbacks[client_] = callback_data;
1246
- rats_set_file_progress_callback(client_, file_progress_callback_wrapper, client_);
856
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
857
+ std::string path = info[1].As<Napi::String>().Utf8Value();
858
+ uint64_t id = rats_send_directory(node_, peer.c_str(), path.c_str());
859
+ return Napi::Number::New(env, static_cast<double>(id));
860
+ }
861
+
862
+ // Shared decoder for the (peerId, transferId[, dest]) control calls.
863
+ static bool parse_xfer_args(const Napi::CallbackInfo& info, std::string& peer,
864
+ uint64_t& transfer_id) {
865
+ Napi::Env env = info.Env();
866
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
867
+ Napi::TypeError::New(env, "Expected peerId (string) and transferId (number)")
868
+ .ThrowAsJavaScriptException();
869
+ return false;
870
+ }
871
+ peer = info[0].As<Napi::String>().Utf8Value();
872
+ transfer_id = static_cast<uint64_t>(info[1].As<Napi::Number>().Int64Value());
873
+ return true;
874
+ }
875
+
876
+ Napi::Value RatsNode::AcceptFile(const Napi::CallbackInfo& info) {
877
+ RATS_REQUIRE_NODE(info.Env().Undefined());
878
+ Napi::Env env = info.Env();
879
+ std::string peer; uint64_t id;
880
+ if (!parse_xfer_args(info, peer, id)) return env.Undefined();
881
+ if (info.Length() < 3 || !info[2].IsString()) {
882
+ Napi::TypeError::New(env, "Expected destPath (string)").ThrowAsJavaScriptException();
883
+ return env.Undefined();
1247
884
  }
885
+ std::string dest = info[2].As<Napi::String>().Utf8Value();
886
+ throw_on_error(env, rats_accept_file(node_, peer.c_str(), id, dest.c_str()));
887
+ return env.Undefined();
888
+ }
1248
889
 
1249
- void OnFileRequest(const Napi::CallbackInfo& info) {
1250
- Napi::Env env = info.Env();
890
+ Napi::Value RatsNode::RejectFile(const Napi::CallbackInfo& info) {
891
+ RATS_REQUIRE_NODE(info.Env().Undefined());
892
+ Napi::Env env = info.Env();
893
+ std::string peer; uint64_t id;
894
+ if (!parse_xfer_args(info, peer, id)) return env.Undefined();
895
+ throw_on_error(env, rats_reject_file(node_, peer.c_str(), id));
896
+ return env.Undefined();
897
+ }
1251
898
 
1252
- if (info.Length() < 1 || !info[0].IsFunction()) {
1253
- Napi::TypeError::New(env, "Expected callback function").ThrowAsJavaScriptException();
1254
- return;
1255
- }
899
+ Napi::Value RatsNode::CancelFile(const Napi::CallbackInfo& info) {
900
+ RATS_REQUIRE_NODE(info.Env().Undefined());
901
+ Napi::Env env = info.Env();
902
+ std::string peer; uint64_t id;
903
+ if (!parse_xfer_args(info, peer, id)) return env.Undefined();
904
+ throw_on_error(env, rats_cancel_file(node_, peer.c_str(), id));
905
+ return env.Undefined();
906
+ }
1256
907
 
1257
- auto callback_data = std::make_shared<CallbackData>(env);
1258
- callback_data->callback = Napi::Persistent(info[0].As<Napi::Function>());
908
+ Napi::Value RatsNode::PauseFile(const Napi::CallbackInfo& info) {
909
+ RATS_REQUIRE_NODE(info.Env().Undefined());
910
+ Napi::Env env = info.Env();
911
+ std::string peer; uint64_t id;
912
+ if (!parse_xfer_args(info, peer, id)) return env.Undefined();
913
+ throw_on_error(env, rats_pause_file(node_, peer.c_str(), id));
914
+ return env.Undefined();
915
+ }
1259
916
 
1260
- file_request_callbacks[client_] = callback_data;
1261
- rats_set_file_request_callback(client_, file_request_callback_wrapper, client_);
1262
- }
917
+ Napi::Value RatsNode::ResumeFile(const Napi::CallbackInfo& info) {
918
+ RATS_REQUIRE_NODE(info.Env().Undefined());
919
+ Napi::Env env = info.Env();
920
+ std::string peer; uint64_t id;
921
+ if (!parse_xfer_args(info, peer, id)) return env.Undefined();
922
+ throw_on_error(env, rats_resume_file(node_, peer.c_str(), id));
923
+ return env.Undefined();
924
+ }
1263
925
 
1264
- // Configuration persistence
1265
- Napi::Value LoadConfiguration(const Napi::CallbackInfo& info) {
1266
- Napi::Env env = info.Env();
1267
- rats_error_t result = rats_load_configuration(client_);
1268
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
1269
- }
1270
-
1271
- Napi::Value SaveConfiguration(const Napi::CallbackInfo& info) {
1272
- Napi::Env env = info.Env();
1273
- rats_error_t result = rats_save_configuration(client_);
1274
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
926
+ // ---------------------------------------------------------------------------
927
+ // Ping / reconnect
928
+ // ---------------------------------------------------------------------------
929
+
930
+ void RatsNode::EnablePing(const Napi::CallbackInfo& info) {
931
+ RATS_REQUIRE_NODE();
932
+ throw_on_error(info.Env(), rats_enable_ping(node_));
933
+ }
934
+
935
+ Napi::Value RatsNode::GetPeerRttMs(const Napi::CallbackInfo& info) {
936
+ RATS_REQUIRE_NODE(info.Env().Undefined());
937
+ Napi::Env env = info.Env();
938
+ if (info.Length() < 1 || !info[0].IsString()) {
939
+ Napi::TypeError::New(env, "Expected peerId (string)").ThrowAsJavaScriptException();
940
+ return env.Undefined();
1275
941
  }
1276
-
1277
- Napi::Value SetDataDirectory(const Napi::CallbackInfo& info) {
1278
- Napi::Env env = info.Env();
1279
-
1280
- if (info.Length() < 1 || !info[0].IsString()) {
1281
- Napi::TypeError::New(env, "Expected directory_path (string)").ThrowAsJavaScriptException();
1282
- return env.Null();
1283
- }
1284
-
1285
- std::string directory_path = info[0].As<Napi::String>().Utf8Value();
1286
- rats_error_t result = rats_set_data_directory(client_, directory_path.c_str());
1287
- return Napi::Boolean::New(env, result == RATS_SUCCESS);
942
+ std::string peer = info[0].As<Napi::String>().Utf8Value();
943
+ int64_t rtt = rats_peer_rtt_ms(node_, peer.c_str());
944
+ return Napi::Number::New(env, static_cast<double>(rtt));
945
+ }
946
+
947
+ void RatsNode::EnableReconnect(const Napi::CallbackInfo& info) {
948
+ RATS_REQUIRE_NODE();
949
+ throw_on_error(info.Env(), rats_enable_reconnect(node_));
950
+ }
951
+
952
+ Napi::Value RatsNode::AddReconnect(const Napi::CallbackInfo& info) {
953
+ RATS_REQUIRE_NODE(info.Env().Undefined());
954
+ Napi::Env env = info.Env();
955
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
956
+ Napi::TypeError::New(env, "Expected host (string) and port (number)")
957
+ .ThrowAsJavaScriptException();
958
+ return env.Undefined();
1288
959
  }
1289
-
1290
- Napi::Value GetDataDirectory(const Napi::CallbackInfo& info) {
1291
- Napi::Env env = info.Env();
1292
- char* directory = rats_get_data_directory(client_);
1293
- if (!directory) return env.Null();
1294
-
1295
- Napi::String result = Napi::String::New(env, directory);
1296
- rats_string_free(directory);
1297
- return result;
960
+ std::string host = info[0].As<Napi::String>().Utf8Value();
961
+ uint16_t port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
962
+ throw_on_error(env, rats_add_reconnect(node_, host.c_str(), port));
963
+ return env.Undefined();
964
+ }
965
+
966
+ Napi::Value RatsNode::RemoveReconnect(const Napi::CallbackInfo& info) {
967
+ RATS_REQUIRE_NODE(info.Env().Undefined());
968
+ Napi::Env env = info.Env();
969
+ if (info.Length() < 2 || !info[0].IsString() || !info[1].IsNumber()) {
970
+ Napi::TypeError::New(env, "Expected host (string) and port (number)")
971
+ .ThrowAsJavaScriptException();
972
+ return env.Undefined();
1298
973
  }
1299
- };
974
+ std::string host = info[0].As<Napi::String>().Utf8Value();
975
+ uint16_t port = static_cast<uint16_t>(info[1].As<Napi::Number>().Uint32Value());
976
+ throw_on_error(env, rats_remove_reconnect(node_, host.c_str(), port));
977
+ return env.Undefined();
978
+ }
979
+
980
+ // ---------------------------------------------------------------------------
981
+ // Class registration
982
+ // ---------------------------------------------------------------------------
983
+
984
+ Napi::Object RatsNode::Init(Napi::Env env, Napi::Object exports) {
985
+ Napi::Function func = DefineClass(env, "RatsNode", {
986
+ // lifecycle / core
987
+ InstanceMethod("start", &RatsNode::Start),
988
+ InstanceMethod("stop", &RatsNode::Stop),
989
+ InstanceMethod("destroy", &RatsNode::Destroy),
990
+ InstanceMethod("listenPort", &RatsNode::GetListenPort),
991
+ InstanceMethod("localId", &RatsNode::GetLocalId),
992
+ InstanceMethod("protocol", &RatsNode::GetProtocol),
993
+ InstanceMethod("transports", &RatsNode::GetTransports),
994
+ // connections
995
+ InstanceMethod("connect", &RatsNode::Connect),
996
+ InstanceMethod("peerCount", &RatsNode::GetPeerCount),
997
+ InstanceMethod("peerIds", &RatsNode::GetPeerIds),
998
+ InstanceMethod("setMaxPeers", &RatsNode::SetMaxPeers),
999
+ InstanceMethod("maxPeers", &RatsNode::GetMaxPeers),
1000
+ InstanceMethod("peerTransport", &RatsNode::GetPeerTransport),
1001
+ InstanceMethod("peerTransports", &RatsNode::GetPeerTransports),
1002
+ // raw channel messaging
1003
+ InstanceMethod("send", &RatsNode::Send),
1004
+ InstanceMethod("broadcast", &RatsNode::Broadcast),
1005
+ InstanceMethod("on", &RatsNode::On),
1006
+ // peer events
1007
+ InstanceMethod("onPeerConnected", &RatsNode::OnPeerConnected),
1008
+ InstanceMethod("onPeerDisconnected", &RatsNode::OnPeerDisconnected),
1009
+ // discovery / NAT
1010
+ InstanceMethod("enableDht", &RatsNode::EnableDht),
1011
+ InstanceMethod("enableMdns", &RatsNode::EnableMdns),
1012
+ InstanceMethod("enablePortMapping", &RatsNode::EnablePortMapping),
1013
+ InstanceMethod("enableHolePunch", &RatsNode::EnableHolePunch),
1014
+ InstanceMethod("punchPeer", &RatsNode::PunchPeer),
1015
+ InstanceMethod("enableRelay", &RatsNode::EnableRelay),
1016
+ InstanceMethod("connectViaRelay", &RatsNode::ConnectViaRelay),
1017
+ InstanceMethod("natMapping", &RatsNode::GetNatMapping),
1018
+ // pub/sub
1019
+ InstanceMethod("enablePubsub", &RatsNode::EnablePubsub),
1020
+ InstanceMethod("subscribe", &RatsNode::Subscribe),
1021
+ InstanceMethod("unsubscribe", &RatsNode::Unsubscribe),
1022
+ InstanceMethod("publish", &RatsNode::Publish),
1023
+ // typed JSON
1024
+ InstanceMethod("enableJson", &RatsNode::EnableJson),
1025
+ InstanceMethod("onJson", &RatsNode::OnJson),
1026
+ InstanceMethod("onceJson", &RatsNode::OnceJson),
1027
+ InstanceMethod("offJson", &RatsNode::OffJson),
1028
+ InstanceMethod("sendJson", &RatsNode::SendJson),
1029
+ InstanceMethod("broadcastJson", &RatsNode::BroadcastJson),
1030
+ // file transfer
1031
+ InstanceMethod("enableFileTransfer", &RatsNode::EnableFileTransfer),
1032
+ InstanceMethod("onFileOffer", &RatsNode::OnFileOffer),
1033
+ InstanceMethod("onFileProgress", &RatsNode::OnFileProgress),
1034
+ InstanceMethod("onFileComplete", &RatsNode::OnFileComplete),
1035
+ InstanceMethod("sendFile", &RatsNode::SendFile),
1036
+ InstanceMethod("sendDirectory", &RatsNode::SendDirectory),
1037
+ InstanceMethod("acceptFile", &RatsNode::AcceptFile),
1038
+ InstanceMethod("rejectFile", &RatsNode::RejectFile),
1039
+ InstanceMethod("cancelFile", &RatsNode::CancelFile),
1040
+ InstanceMethod("pauseFile", &RatsNode::PauseFile),
1041
+ InstanceMethod("resumeFile", &RatsNode::ResumeFile),
1042
+ // ping / reconnect
1043
+ InstanceMethod("enablePing", &RatsNode::EnablePing),
1044
+ InstanceMethod("peerRttMs", &RatsNode::GetPeerRttMs),
1045
+ InstanceMethod("enableReconnect", &RatsNode::EnableReconnect),
1046
+ InstanceMethod("addReconnect", &RatsNode::AddReconnect),
1047
+ InstanceMethod("removeReconnect", &RatsNode::RemoveReconnect),
1048
+ });
1049
+
1050
+ exports.Set("RatsNode", func);
1051
+ return exports;
1052
+ }
1053
+
1054
+ // ---------------------------------------------------------------------------
1055
+ // Module-level functions (process-global; no node required)
1056
+ // ---------------------------------------------------------------------------
1300
1057
 
1301
- // Library utility functions
1302
1058
  Napi::Value GetVersionString(const Napi::CallbackInfo& info) {
1303
- Napi::Env env = info.Env();
1304
- const char* version = rats_get_version_string();
1305
- return Napi::String::New(env, version);
1059
+ return Napi::String::New(info.Env(), rats_version_string());
1306
1060
  }
1307
1061
 
1308
1062
  Napi::Value GetVersion(const Napi::CallbackInfo& info) {
1309
1063
  Napi::Env env = info.Env();
1310
- int major, minor, patch, build;
1311
- rats_get_version(&major, &minor, &patch, &build);
1312
-
1064
+ int major = 0, minor = 0, patch = 0, build = 0;
1065
+ rats_version(&major, &minor, &patch, &build);
1313
1066
  Napi::Object version = Napi::Object::New(env);
1314
1067
  version.Set("major", Napi::Number::New(env, major));
1315
1068
  version.Set("minor", Napi::Number::New(env, minor));
1316
1069
  version.Set("patch", Napi::Number::New(env, patch));
1317
1070
  version.Set("build", Napi::Number::New(env, build));
1318
-
1319
1071
  return version;
1320
1072
  }
1321
1073
 
1322
- // Console logging control functions
1323
- Napi::Value SetConsoleLoggingEnabled(const Napi::CallbackInfo& info) {
1324
- Napi::Env env = info.Env();
1325
-
1326
- if (info.Length() < 1 || !info[0].IsBoolean()) {
1327
- Napi::TypeError::New(env, "Boolean expected").ThrowAsJavaScriptException();
1328
- return env.Undefined();
1329
- }
1330
-
1331
- bool enabled = info[0].As<Napi::Boolean>().Value();
1332
- rats_set_console_logging_enabled(enabled ? 1 : 0);
1333
- return env.Undefined();
1074
+ Napi::Value GetGitDescribe(const Napi::CallbackInfo& info) {
1075
+ return Napi::String::New(info.Env(), rats_git_describe());
1334
1076
  }
1335
1077
 
1336
- Napi::Value IsConsoleLoggingEnabled(const Napi::CallbackInfo& info) {
1337
- Napi::Env env = info.Env();
1338
- return Napi::Boolean::New(env, rats_is_console_logging_enabled() != 0);
1078
+ Napi::Value GetAbi(const Napi::CallbackInfo& info) {
1079
+ return Napi::Number::New(info.Env(), rats_abi());
1339
1080
  }
1340
1081
 
1341
- Napi::Value GetGitDescribe(const Napi::CallbackInfo& info) {
1082
+ Napi::Value SetLogLevel(const Napi::CallbackInfo& info) {
1342
1083
  Napi::Env env = info.Env();
1343
- const char* git_describe = rats_get_git_describe();
1344
- return Napi::String::New(env, git_describe);
1084
+ if (info.Length() < 1 || !info[0].IsNumber()) {
1085
+ Napi::TypeError::New(env, "Expected log level (number)").ThrowAsJavaScriptException();
1086
+ return env.Undefined();
1087
+ }
1088
+ rats_set_log_level(static_cast<rats_log_level_t>(info[0].As<Napi::Number>().Int32Value()));
1089
+ return env.Undefined();
1345
1090
  }
1346
1091
 
1347
- Napi::Value GetAbi(const Napi::CallbackInfo& info) {
1092
+ Napi::Value SetLogFile(const Napi::CallbackInfo& info) {
1348
1093
  Napi::Env env = info.Env();
1349
- uint32_t abi = rats_get_abi();
1350
- return Napi::Number::New(env, abi);
1094
+ if (info.Length() >= 1 && info[0].IsString()) {
1095
+ std::string path = info[0].As<Napi::String>().Utf8Value();
1096
+ rats_set_log_file(path.c_str());
1097
+ } else {
1098
+ rats_set_log_file(nullptr); // NULL/empty disables file logging
1099
+ }
1100
+ return env.Undefined();
1351
1101
  }
1352
1102
 
1353
- // Constants
1103
+ // Constants exposed to JS (security + log levels + error codes).
1354
1104
  Napi::Object InitConstants(Napi::Env env) {
1355
1105
  Napi::Object constants = Napi::Object::New(env);
1356
-
1357
- // Error codes
1106
+
1107
+ Napi::Object security = Napi::Object::New(env);
1108
+ security.Set("NOISE", Napi::Number::New(env, RATS_SECURITY_NOISE));
1109
+ security.Set("PLAINTEXT", Napi::Number::New(env, RATS_SECURITY_PLAINTEXT));
1110
+ constants.Set("SECURITY", security);
1111
+
1112
+ Napi::Object transport = Napi::Object::New(env);
1113
+ transport.Set("TCP", Napi::Number::New(env, RATS_TRANSPORT_TCP));
1114
+ transport.Set("UDP", Napi::Number::New(env, RATS_TRANSPORT_UDP));
1115
+ constants.Set("TRANSPORT", transport);
1116
+
1117
+ Napi::Object transportMask = Napi::Object::New(env);
1118
+ transportMask.Set("TCP", Napi::Number::New(env, RATS_TRANSPORT_MASK_TCP));
1119
+ transportMask.Set("UDP", Napi::Number::New(env, RATS_TRANSPORT_MASK_UDP));
1120
+ constants.Set("TRANSPORT_MASK", transportMask);
1121
+
1122
+ Napi::Object nat = Napi::Object::New(env);
1123
+ nat.Set("UNKNOWN", Napi::Number::New(env, RATS_NAT_UNKNOWN));
1124
+ nat.Set("OPEN", Napi::Number::New(env, RATS_NAT_OPEN));
1125
+ nat.Set("ENDPOINT_INDEPENDENT", Napi::Number::New(env, RATS_NAT_ENDPOINT_INDEPENDENT));
1126
+ nat.Set("ENDPOINT_DEPENDENT", Napi::Number::New(env, RATS_NAT_ENDPOINT_DEPENDENT));
1127
+ constants.Set("NAT_MAPPING", nat);
1128
+
1129
+ Napi::Object logLevels = Napi::Object::New(env);
1130
+ logLevels.Set("DEBUG", Napi::Number::New(env, RATS_LOG_DEBUG));
1131
+ logLevels.Set("INFO", Napi::Number::New(env, RATS_LOG_INFO));
1132
+ logLevels.Set("WARN", Napi::Number::New(env, RATS_LOG_WARN));
1133
+ logLevels.Set("ERROR", Napi::Number::New(env, RATS_LOG_ERROR));
1134
+ constants.Set("LOG_LEVELS", logLevels);
1135
+
1358
1136
  Napi::Object errors = Napi::Object::New(env);
1359
- errors.Set("SUCCESS", Napi::Number::New(env, RATS_SUCCESS));
1360
- errors.Set("INVALID_HANDLE", Napi::Number::New(env, RATS_ERROR_INVALID_HANDLE));
1361
- errors.Set("INVALID_PARAMETER", Napi::Number::New(env, RATS_ERROR_INVALID_PARAMETER));
1362
- errors.Set("NOT_RUNNING", Napi::Number::New(env, RATS_ERROR_NOT_RUNNING));
1363
- errors.Set("OPERATION_FAILED", Napi::Number::New(env, RATS_ERROR_OPERATION_FAILED));
1364
- errors.Set("PEER_NOT_FOUND", Napi::Number::New(env, RATS_ERROR_PEER_NOT_FOUND));
1365
- errors.Set("MEMORY_ALLOCATION", Napi::Number::New(env, RATS_ERROR_MEMORY_ALLOCATION));
1366
- errors.Set("JSON_PARSE", Napi::Number::New(env, RATS_ERROR_JSON_PARSE));
1137
+ errors.Set("OK", Napi::Number::New(env, RATS_OK));
1138
+ errors.Set("INVALID_ARG", Napi::Number::New(env, RATS_ERR_INVALID_ARG));
1139
+ errors.Set("NOT_STARTED", Napi::Number::New(env, RATS_ERR_NOT_STARTED));
1140
+ errors.Set("ALREADY_STARTED", Napi::Number::New(env, RATS_ERR_ALREADY_STARTED));
1141
+ errors.Set("NOT_ENABLED", Napi::Number::New(env, RATS_ERR_NOT_ENABLED));
1142
+ errors.Set("NO_SUCH_PEER", Napi::Number::New(env, RATS_ERR_NO_SUCH_PEER));
1143
+ errors.Set("BIND", Napi::Number::New(env, RATS_ERR_BIND));
1144
+ errors.Set("INTERNAL", Napi::Number::New(env, RATS_ERR_INTERNAL));
1367
1145
  constants.Set("ERRORS", errors);
1368
-
1369
- // ICE connection states
1370
- Napi::Object iceStates = Napi::Object::New(env);
1371
- iceStates.Set("NEW", Napi::Number::New(env, RATS_ICE_STATE_NEW));
1372
- iceStates.Set("GATHERING", Napi::Number::New(env, RATS_ICE_STATE_GATHERING));
1373
- iceStates.Set("CHECKING", Napi::Number::New(env, RATS_ICE_STATE_CHECKING));
1374
- iceStates.Set("CONNECTED", Napi::Number::New(env, RATS_ICE_STATE_CONNECTED));
1375
- iceStates.Set("COMPLETED", Napi::Number::New(env, RATS_ICE_STATE_COMPLETED));
1376
- iceStates.Set("FAILED", Napi::Number::New(env, RATS_ICE_STATE_FAILED));
1377
- iceStates.Set("DISCONNECTED", Napi::Number::New(env, RATS_ICE_STATE_DISCONNECTED));
1378
- iceStates.Set("CLOSED", Napi::Number::New(env, RATS_ICE_STATE_CLOSED));
1379
- constants.Set("ICE_CONNECTION_STATES", iceStates);
1380
-
1381
- // ICE gathering states
1382
- Napi::Object iceGatheringStates = Napi::Object::New(env);
1383
- iceGatheringStates.Set("NEW", Napi::Number::New(env, RATS_ICE_GATHERING_NEW));
1384
- iceGatheringStates.Set("GATHERING", Napi::Number::New(env, RATS_ICE_GATHERING_GATHERING));
1385
- iceGatheringStates.Set("COMPLETE", Napi::Number::New(env, RATS_ICE_GATHERING_COMPLETE));
1386
- constants.Set("ICE_GATHERING_STATES", iceGatheringStates);
1387
-
1388
- // ICE candidate types
1389
- Napi::Object iceCandidateTypes = Napi::Object::New(env);
1390
- iceCandidateTypes.Set("HOST", Napi::Number::New(env, RATS_ICE_CANDIDATE_HOST));
1391
- iceCandidateTypes.Set("SRFLX", Napi::Number::New(env, RATS_ICE_CANDIDATE_SRFLX));
1392
- iceCandidateTypes.Set("PRFLX", Napi::Number::New(env, RATS_ICE_CANDIDATE_PRFLX));
1393
- iceCandidateTypes.Set("RELAY", Napi::Number::New(env, RATS_ICE_CANDIDATE_RELAY));
1394
- constants.Set("ICE_CANDIDATE_TYPES", iceCandidateTypes);
1395
-
1146
+
1396
1147
  return constants;
1397
1148
  }
1398
1149
 
1399
- // Module initialization
1400
1150
  Napi::Object Init(Napi::Env env, Napi::Object exports) {
1401
- // Export the RatsClient class
1402
- RatsClient::Init(env, exports);
1403
-
1404
- // Export utility functions
1151
+ RatsNode::Init(env, exports);
1152
+
1405
1153
  exports.Set("getVersionString", Napi::Function::New(env, GetVersionString));
1406
1154
  exports.Set("getVersion", Napi::Function::New(env, GetVersion));
1407
1155
  exports.Set("getGitDescribe", Napi::Function::New(env, GetGitDescribe));
1408
1156
  exports.Set("getAbi", Napi::Function::New(env, GetAbi));
1409
-
1410
- // Export logging control functions
1411
- exports.Set("setConsoleLoggingEnabled", Napi::Function::New(env, SetConsoleLoggingEnabled));
1412
- exports.Set("isConsoleLoggingEnabled", Napi::Function::New(env, IsConsoleLoggingEnabled));
1413
-
1414
- // Export constants
1157
+ exports.Set("setLogLevel", Napi::Function::New(env, SetLogLevel));
1158
+ exports.Set("setLogFile", Napi::Function::New(env, SetLogFile));
1415
1159
  exports.Set("constants", InitConstants(env));
1416
-
1160
+
1417
1161
  return exports;
1418
1162
  }
1419
1163