@encharm/cws 4.5.3 → 4.8.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 (305) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/CLAUDE.md +83 -0
  3. package/README.md +20 -4
  4. package/binding.gyp +8 -2
  5. package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
  6. package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
  7. package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
  8. package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
  9. package/dist/bindings/cws_linux_arm64_node115.node +0 -0
  10. package/dist/bindings/cws_linux_arm64_node127.node +0 -0
  11. package/dist/bindings/cws_linux_arm64_node137.node +0 -0
  12. package/dist/bindings/cws_linux_arm64_node147.node +0 -0
  13. package/dist/bindings/cws_linux_x64_node115.node +0 -0
  14. package/dist/bindings/cws_linux_x64_node127.node +0 -0
  15. package/dist/bindings/cws_linux_x64_node137.node +0 -0
  16. package/dist/bindings/cws_linux_x64_node147.node +0 -0
  17. package/dist/bindings/cws_win32_x64_node115.node +0 -0
  18. package/dist/bindings/cws_win32_x64_node127.node +0 -0
  19. package/dist/bindings/cws_win32_x64_node137.node +0 -0
  20. package/dist/bindings/cws_win32_x64_node147.node +0 -0
  21. package/dist/client.d.ts +1 -0
  22. package/dist/client.js +15 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +2 -0
  25. package/dist/server.d.ts +1 -0
  26. package/dist/server.js +16 -2
  27. package/dist/shared.d.ts +1 -0
  28. package/dist/shared.js +3 -2
  29. package/package.json +1 -1
  30. package/src/Addon.cpp +4 -1
  31. package/src/Addon.h +80 -15
  32. package/src/Extensions.cpp +7 -1
  33. package/src/Extensions.h +2 -1
  34. package/src/Group.cpp +1 -1
  35. package/src/Group.h +4 -1
  36. package/src/HTTPSocket.cpp +1 -1
  37. package/src/Hub.cpp +11 -76
  38. package/src/Hub.h +12 -10
  39. package/src/Networking.h +11 -0
  40. package/src/Socket.h +201 -6
  41. package/src/WebSocket.cpp +8 -5
  42. package/src/Zlib.cpp +138 -0
  43. package/src/Zlib.h +38 -0
  44. package/src/cSNode.cpp +2 -0
  45. package/src/cSNode.h +4 -0
  46. package/src/headers/26/acorn_version.h +6 -0
  47. package/src/headers/26/aliased_buffer-inl.h +264 -0
  48. package/src/headers/26/aliased_buffer.h +205 -0
  49. package/src/headers/26/aliased_struct-inl.h +143 -0
  50. package/src/headers/26/aliased_struct.h +248 -0
  51. package/src/headers/26/amaro_version.h +6 -0
  52. package/src/headers/26/async_context_frame.h +33 -0
  53. package/src/headers/26/async_wrap-inl.h +123 -0
  54. package/src/headers/26/async_wrap.h +254 -0
  55. package/src/headers/26/base_object-inl.h +333 -0
  56. package/src/headers/26/base_object.h +339 -0
  57. package/src/headers/26/base_object_types.h +76 -0
  58. package/src/headers/26/blob_serializer_deserializer-inl.h +385 -0
  59. package/src/headers/26/blob_serializer_deserializer.h +142 -0
  60. package/src/headers/26/builtin_info.h +57 -0
  61. package/src/headers/26/callback_queue-inl.h +97 -0
  62. package/src/headers/26/callback_queue.h +79 -0
  63. package/src/headers/26/cares_wrap.h +475 -0
  64. package/src/headers/26/cleanup_queue-inl.h +36 -0
  65. package/src/headers/26/cleanup_queue.h +79 -0
  66. package/src/headers/26/compile_cache.h +134 -0
  67. package/src/headers/26/connect_wrap.h +26 -0
  68. package/src/headers/26/connection_wrap.h +30 -0
  69. package/src/headers/26/cppgc_helpers-inl.h +69 -0
  70. package/src/headers/26/cppgc_helpers.h +162 -0
  71. package/src/headers/26/crypto/crypto_aes.h +107 -0
  72. package/src/headers/26/crypto/crypto_argon2.h +88 -0
  73. package/src/headers/26/crypto/crypto_bio.h +193 -0
  74. package/src/headers/26/crypto/crypto_chacha20_poly1305.h +62 -0
  75. package/src/headers/26/crypto/crypto_cipher.h +291 -0
  76. package/src/headers/26/crypto/crypto_common.h +48 -0
  77. package/src/headers/26/crypto/crypto_context.h +219 -0
  78. package/src/headers/26/crypto/crypto_dh.h +104 -0
  79. package/src/headers/26/crypto/crypto_dsa.h +50 -0
  80. package/src/headers/26/crypto/crypto_ec.h +102 -0
  81. package/src/headers/26/crypto/crypto_hash.h +157 -0
  82. package/src/headers/26/crypto/crypto_hkdf.h +62 -0
  83. package/src/headers/26/crypto/crypto_hmac.h +92 -0
  84. package/src/headers/26/crypto/crypto_kem.h +131 -0
  85. package/src/headers/26/crypto/crypto_keygen.h +382 -0
  86. package/src/headers/26/crypto/crypto_keys.h +366 -0
  87. package/src/headers/26/crypto/crypto_kmac.h +79 -0
  88. package/src/headers/26/crypto/crypto_pbkdf2.h +76 -0
  89. package/src/headers/26/crypto/crypto_pqc.h +39 -0
  90. package/src/headers/26/crypto/crypto_random.h +127 -0
  91. package/src/headers/26/crypto/crypto_rsa.h +108 -0
  92. package/src/headers/26/crypto/crypto_scrypt.h +85 -0
  93. package/src/headers/26/crypto/crypto_sig.h +159 -0
  94. package/src/headers/26/crypto/crypto_spkac.h +21 -0
  95. package/src/headers/26/crypto/crypto_timing.h +20 -0
  96. package/src/headers/26/crypto/crypto_tls.h +342 -0
  97. package/src/headers/26/crypto/crypto_turboshake.h +105 -0
  98. package/src/headers/26/crypto/crypto_util.h +775 -0
  99. package/src/headers/26/crypto/crypto_x509.h +140 -0
  100. package/src/headers/26/dataqueue/queue.h +311 -0
  101. package/src/headers/26/debug_utils-inl.h +291 -0
  102. package/src/headers/26/debug_utils.h +200 -0
  103. package/src/headers/26/diagnosticfilename-inl.h +33 -0
  104. package/src/headers/26/embedded_data.h +17 -0
  105. package/src/headers/26/encoding_binding.h +59 -0
  106. package/src/headers/26/env-inl.h +956 -0
  107. package/src/headers/26/env.h +1294 -0
  108. package/src/headers/26/env_properties.h +562 -0
  109. package/src/headers/26/ffi/data.h +30 -0
  110. package/src/headers/26/ffi/fast.h +96 -0
  111. package/src/headers/26/ffi/jit_memory.h +26 -0
  112. package/src/headers/26/ffi/types.h +99 -0
  113. package/src/headers/26/handle_wrap.h +123 -0
  114. package/src/headers/26/histogram-inl.h +211 -0
  115. package/src/headers/26/histogram.h +474 -0
  116. package/src/headers/26/inspector/dom_storage_agent.h +68 -0
  117. package/src/headers/26/inspector/inspector_object_utils.h +38 -0
  118. package/src/headers/26/inspector/io_agent.h +30 -0
  119. package/src/headers/26/inspector/main_thread_interface.h +116 -0
  120. package/src/headers/26/inspector/network_agent.h +110 -0
  121. package/src/headers/26/inspector/network_inspector.h +45 -0
  122. package/src/headers/26/inspector/network_requests_buffer.h +195 -0
  123. package/src/headers/26/inspector/network_resource_manager.h +29 -0
  124. package/src/headers/26/inspector/node_json.h +24 -0
  125. package/src/headers/26/inspector/node_string.h +101 -0
  126. package/src/headers/26/inspector/notification_emitter.h +38 -0
  127. package/src/headers/26/inspector/protocol_helper.h +27 -0
  128. package/src/headers/26/inspector/runtime_agent.h +41 -0
  129. package/src/headers/26/inspector/storage_agent.h +33 -0
  130. package/src/headers/26/inspector/target_agent.h +59 -0
  131. package/src/headers/26/inspector/target_manager.h +70 -0
  132. package/src/headers/26/inspector/tracing_agent.h +43 -0
  133. package/src/headers/26/inspector/worker_agent.h +40 -0
  134. package/src/headers/26/inspector/worker_inspector.h +124 -0
  135. package/src/headers/26/inspector_agent.h +168 -0
  136. package/src/headers/26/inspector_io.h +78 -0
  137. package/src/headers/26/inspector_profiler.h +149 -0
  138. package/src/headers/26/inspector_socket.h +60 -0
  139. package/src/headers/26/inspector_socket_server.h +110 -0
  140. package/src/headers/26/js_native_api.h +634 -0
  141. package/src/headers/26/js_native_api_types.h +244 -0
  142. package/src/headers/26/js_native_api_v8.h +485 -0
  143. package/src/headers/26/js_native_api_v8_internals.h +45 -0
  144. package/src/headers/26/js_stream.h +57 -0
  145. package/src/headers/26/json_utils.h +172 -0
  146. package/src/headers/26/large_pages/node_large_page.h +34 -0
  147. package/src/headers/26/lru_cache-inl.h +69 -0
  148. package/src/headers/26/memory_tracker-inl.h +461 -0
  149. package/src/headers/26/memory_tracker.h +341 -0
  150. package/src/headers/26/module_wrap.h +231 -0
  151. package/src/headers/26/ncrypto.h +2008 -0
  152. package/src/headers/26/node.h +1693 -0
  153. package/src/headers/26/node_api.h +265 -0
  154. package/src/headers/26/node_api_internals.h +48 -0
  155. package/src/headers/26/node_api_types.h +58 -0
  156. package/src/headers/26/node_binding.h +170 -0
  157. package/src/headers/26/node_blob.h +160 -0
  158. package/src/headers/26/node_bob-inl.h +36 -0
  159. package/src/headers/26/node_bob.h +107 -0
  160. package/src/headers/26/node_buffer.h +92 -0
  161. package/src/headers/26/node_builtins.h +239 -0
  162. package/src/headers/26/node_concepts.h +46 -0
  163. package/src/headers/26/node_config_file.h +77 -0
  164. package/src/headers/26/node_constants.h +82 -0
  165. package/src/headers/26/node_context_data.h +171 -0
  166. package/src/headers/26/node_contextify.h +261 -0
  167. package/src/headers/26/node_crypto.h +66 -0
  168. package/src/headers/26/node_debug.h +30 -0
  169. package/src/headers/26/node_diagnostics_channel.h +121 -0
  170. package/src/headers/26/node_dir.h +52 -0
  171. package/src/headers/26/node_dotenv.h +45 -0
  172. package/src/headers/26/node_errors.h +378 -0
  173. package/src/headers/26/node_exit_code.h +54 -0
  174. package/src/headers/26/node_external_reference.h +171 -0
  175. package/src/headers/26/node_ffi.h +212 -0
  176. package/src/headers/26/node_file-inl.h +421 -0
  177. package/src/headers/26/node_file.h +561 -0
  178. package/src/headers/26/node_file_utils.h +39 -0
  179. package/src/headers/26/node_hash.h +212 -0
  180. package/src/headers/26/node_http2.h +1202 -0
  181. package/src/headers/26/node_http2_state.h +159 -0
  182. package/src/headers/26/node_http_common-inl.h +199 -0
  183. package/src/headers/26/node_http_common.h +535 -0
  184. package/src/headers/26/node_i18n.h +132 -0
  185. package/src/headers/26/node_internals.h +485 -0
  186. package/src/headers/26/node_locks.h +184 -0
  187. package/src/headers/26/node_main_instance.h +67 -0
  188. package/src/headers/26/node_mem-inl.h +113 -0
  189. package/src/headers/26/node_mem.h +45 -0
  190. package/src/headers/26/node_messaging.h +389 -0
  191. package/src/headers/26/node_metadata.h +172 -0
  192. package/src/headers/26/node_modules.h +102 -0
  193. package/src/headers/26/node_mutex.h +323 -0
  194. package/src/headers/26/node_object_wrap.h +145 -0
  195. package/src/headers/26/node_options-inl.h +524 -0
  196. package/src/headers/26/node_options.h +710 -0
  197. package/src/headers/26/node_perf.h +167 -0
  198. package/src/headers/26/node_perf_common.h +103 -0
  199. package/src/headers/26/node_platform.h +290 -0
  200. package/src/headers/26/node_process-inl.h +26 -0
  201. package/src/headers/26/node_process.h +112 -0
  202. package/src/headers/26/node_profiling.h +38 -0
  203. package/src/headers/26/node_realm-inl.h +156 -0
  204. package/src/headers/26/node_realm.h +236 -0
  205. package/src/headers/26/node_report.h +43 -0
  206. package/src/headers/26/node_revert.h +82 -0
  207. package/src/headers/26/node_root_certs.h +2937 -0
  208. package/src/headers/26/node_sea.h +101 -0
  209. package/src/headers/26/node_shadow_realm.h +46 -0
  210. package/src/headers/26/node_snapshot_builder.h +57 -0
  211. package/src/headers/26/node_snapshotable.h +167 -0
  212. package/src/headers/26/node_sockaddr-inl.h +248 -0
  213. package/src/headers/26/node_sockaddr.h +459 -0
  214. package/src/headers/26/node_sqlite.h +607 -0
  215. package/src/headers/26/node_stat_watcher.h +73 -0
  216. package/src/headers/26/node_task_runner.h +92 -0
  217. package/src/headers/26/node_threadsafe_cow-inl.h +54 -0
  218. package/src/headers/26/node_threadsafe_cow.h +105 -0
  219. package/src/headers/26/node_union_bytes.h +81 -0
  220. package/src/headers/26/node_url.h +101 -0
  221. package/src/headers/26/node_url_pattern.h +118 -0
  222. package/src/headers/26/node_v8.h +78 -0
  223. package/src/headers/26/node_v8_embedder.h +26 -0
  224. package/src/headers/26/node_v8_platform-inl.h +96 -0
  225. package/src/headers/26/node_version.h +116 -0
  226. package/src/headers/26/node_wasi.h +184 -0
  227. package/src/headers/26/node_wasm_web_api.h +55 -0
  228. package/src/headers/26/node_watchdog.h +164 -0
  229. package/src/headers/26/node_webstorage.h +62 -0
  230. package/src/headers/26/node_worker.h +162 -0
  231. package/src/headers/26/path.h +36 -0
  232. package/src/headers/26/permission/boolean_permission.h +50 -0
  233. package/src/headers/26/permission/fs_permission.h +192 -0
  234. package/src/headers/26/permission/permission.h +155 -0
  235. package/src/headers/26/permission/permission_base.h +79 -0
  236. package/src/headers/26/pipe_wrap.h +80 -0
  237. package/src/headers/26/quic/application.h +279 -0
  238. package/src/headers/26/quic/arena.h +373 -0
  239. package/src/headers/26/quic/bindingdata.h +440 -0
  240. package/src/headers/26/quic/cid.h +127 -0
  241. package/src/headers/26/quic/data.h +358 -0
  242. package/src/headers/26/quic/defs.h +417 -0
  243. package/src/headers/26/quic/endpoint.h +519 -0
  244. package/src/headers/26/quic/guard.h +5 -0
  245. package/src/headers/26/quic/http3.h +25 -0
  246. package/src/headers/26/quic/packet.h +166 -0
  247. package/src/headers/26/quic/preferredaddress.h +72 -0
  248. package/src/headers/26/quic/session.h +770 -0
  249. package/src/headers/26/quic/session_manager.h +107 -0
  250. package/src/headers/26/quic/sessionticket.h +110 -0
  251. package/src/headers/26/quic/streams.h +518 -0
  252. package/src/headers/26/quic/tlscontext.h +353 -0
  253. package/src/headers/26/quic/tokens.h +263 -0
  254. package/src/headers/26/quic/transportparams.h +184 -0
  255. package/src/headers/26/req_wrap-inl.h +175 -0
  256. package/src/headers/26/req_wrap.h +79 -0
  257. package/src/headers/26/spawn_sync.h +243 -0
  258. package/src/headers/26/stream_base-inl.h +170 -0
  259. package/src/headers/26/stream_base.h +487 -0
  260. package/src/headers/26/stream_pipe.h +76 -0
  261. package/src/headers/26/stream_wrap.h +137 -0
  262. package/src/headers/26/string_bytes.h +114 -0
  263. package/src/headers/26/string_decoder-inl.h +31 -0
  264. package/src/headers/26/string_decoder.h +49 -0
  265. package/src/headers/26/tcp_wrap.h +138 -0
  266. package/src/headers/26/threadpoolwork-inl.h +70 -0
  267. package/src/headers/26/timer_wrap-inl.h +32 -0
  268. package/src/headers/26/timer_wrap.h +87 -0
  269. package/src/headers/26/timers.h +70 -0
  270. package/src/headers/26/tracing/agent.h +125 -0
  271. package/src/headers/26/tracing/agent_legacy.h +146 -0
  272. package/src/headers/26/tracing/agent_perfetto.h +145 -0
  273. package/src/headers/26/tracing/node_trace_buffer.h +91 -0
  274. package/src/headers/26/tracing/node_trace_writer.h +79 -0
  275. package/src/headers/26/tracing/trace_event.h +27 -0
  276. package/src/headers/26/tracing/trace_event_helper.h +40 -0
  277. package/src/headers/26/tracing/trace_event_legacy.h +1113 -0
  278. package/src/headers/26/tracing/trace_event_legacy_inl.h +711 -0
  279. package/src/headers/26/tracing/trace_event_perfetto.h +47 -0
  280. package/src/headers/26/tracing/traced_value.h +167 -0
  281. package/src/headers/26/tty_wrap.h +65 -0
  282. package/src/headers/26/udp_wrap.h +231 -0
  283. package/src/headers/26/undici_version.h +6 -0
  284. package/src/headers/26/util-inl.h +788 -0
  285. package/src/headers/26/util.h +1207 -0
  286. package/src/headers/26/v8-cpp-heap-external.h +56 -0
  287. package/src/headers/26/v8-external-memory-accounter.h +60 -0
  288. package/src/headers/26/v8-fast-api-calls.h +784 -0
  289. package/src/headers/26/v8-inspector-protocol.h +13 -0
  290. package/src/headers/26/v8-inspector.h +458 -0
  291. package/src/headers/26/v8-metrics.h +273 -0
  292. package/src/headers/26/v8-trace-categories.h +28 -0
  293. package/src/headers/26/v8-unwinder-state.h +30 -0
  294. package/src/headers/26/v8-util.h +549 -0
  295. package/src/headers/26/v8-value-serializer-version.h +24 -0
  296. package/src/headers/26/v8-version-string.h +38 -0
  297. package/src/headers/26/v8-wasm-trap-handler-posix.h +31 -0
  298. package/src/headers/26/v8-wasm-trap-handler-win.h +28 -0
  299. package/src/headers/26/zlib_version.h +6 -0
  300. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Info.plist +0 -20
  301. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node115.node +0 -0
  302. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/Relocations/aarch64/cws_darwin_arm64_node115.node.yml +0 -931
  303. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Info.plist +0 -20
  304. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node127.node +0 -0
  305. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Resources/Relocations/aarch64/cws_darwin_arm64_node127.node.yml +0 -931
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## Released 4.8.0
2
+ * Prebuilt bindings now compress with zlib-ng 2.2.4 (vendored in `deps/zlib-ng`, statically linked, native `zng_` API so it cannot collide with Node's zlib). Measured on a real RPC stream: level 1 costs 2.2-2.5 us/KB vs 5.6-8 us/KB with zlib. The node-gyp fallback build keeps using Node's zlib through the same `src/Zlib.cpp` wrapper.
3
+ * New `perMessageDeflate.level` option (default 2) and a `zlibBackend` export naming the compiled-in implementation.
4
+ * Fix the node-gyp fallback build (`npm install` on a platform without a prebuilt binding): it lacked the vendored Node header include path, `HAVE_OPENSSL`/`NODE_WANT_INTERNALS`, and a macOS 10.15 deployment target, so it has failed silently since the Node 20 headers were introduced.
5
+
6
+ ## Released 4.7.0
7
+ * permessage-deflate: messages are now compressed by default once negotiated (previously only `send(..., { compress: true })` compressed anything). New options `threshold` (minimum size to compress), `windowBits` and `memLevel` (per-socket memory tier for the sliding window, advertised as `server_max_window_bits` when below 15).
8
+ * Remove a leftover debug `console.log` on module load.
9
+
10
+ ## Released 4.6.0
11
+ * Add Node 26 support (ABI 147, V8 14.6): vendored `src/headers/26`, `MakeCallback` with explicit `async_context`, tagged internal-field reads via `BaseObject::FromJSObject`.
12
+ * Cork writes per event-loop iteration: sends to the same socket within one tick go out in a single gathered write. Disable with `CWS_CORK=0`.
13
+ * Binary messages are detached after the `message` handler returns. A retained `ArrayBuffer` now throws on use instead of exposing another connection's bytes (the receive buffer is shared). Copy with `slice()` if the data is needed later.
14
+ * Fix use-after-free when a send callback closes the socket while its write queue is draining.
15
+ * `terminate()` on an already closed socket is a no-op.
16
+ * The `message` handler is now dispatched through `MakeCallback` like every other handler: correct AsyncLocalStorage propagation, exceptions reach `uncaughtException`, and microtasks drain after each message instead of after the whole poll phase. No measurable throughput cost.
17
+ * Fix `send()` of a typed array that is a subarray (non-zero `byteOffset`): the bytes were read from the start of the underlying ArrayBuffer instead of the view's offset.
18
+
1
19
  ## Released 4.1.0
2
20
  * Add support for socket.bufferedAmount
3
21
 
package/CLAUDE.md ADDED
@@ -0,0 +1,83 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `@encharm/cws` is a native C++ WebSocket implementation for Node.js that aims to be a near drop-in replacement for the `ws` module. It is a fork of ClusterWS/cWS, which itself forked uWebSockets v0.14 (dual MIT + ZLIB license, see `LICENSE` and `src/LICENSE`). Prebuilt binaries for every supported platform/ABI are committed under `dist/bindings/` and published with the package; end users never compile.
8
+
9
+ ## Commands
10
+
11
+ ```sh
12
+ npm run build-ts # lint + tsc: lib/*.ts -> dist/*.js + *.d.ts (dist JS is committed)
13
+ npm run lint # tslint over lib/**/*.ts
14
+ npm test # nyc + mocha, runs tests/**/*.test.ts via ts-node (loads ../lib, not dist)
15
+ npx mocha -r ts-node/register tests/server-client.test.ts -g "ping/pong" --exit # single test
16
+ npm run build-cpp # node-gyp rebuild for the *current* Node ABI only; output goes to build_log.txt, never fails the install
17
+ npm run clean # deletes dist/bindings/* (all committed binaries!) -- don't run casually
18
+ ```
19
+
20
+ Tests bind ports 3000 (ws) and 3001 (wss, certs in `tests/certs/`). The test file currently iterates only `['Non-SSL']`; the SSL loop is wired up but disabled. `tests/autobahn-*.js` are manual Autobahn testsuite harnesses, not part of `npm test`.
21
+
22
+ ### Building the release binaries (multi-ABI)
23
+
24
+ `binding.gyp` is only used by `npm install` for a local build against the running Node (Node's zlib, no zlib-ng); it must carry the same include path and defines as the Makefile or it fails silently (the install script discards the exit code). Release binaries are built by the `Makefile` (macOS/Linux) and `make.bat` (Windows), which:
25
+
26
+ 1. Download official Node header tarballs for one pinned version per supported major into `targets/` (`VER_115`=Node 20, `VER_127`=Node 22, `VER_137`=Node 24, `VER_147`=Node 26; the number is the Node ABI / `process.versions.modules`).
27
+ 2. Compile `src/*.cpp` once per ABI with `g++`/`cl` directly, with `-I src/headers/$V` for the matching Node major.
28
+ 3. Build the vendored zlib-ng (`deps/zlib-ng`, native `zng_` API) once per OS/arch: CMake into `deps/zlib-ng/build-<OS>-<arch>/` on macOS/Linux, `nmake -f win32\Makefile.msc` on Windows. The bindings are compiled with `-DCWS_ZLIB_NG` and link it statically. `src/Zlib.cpp` is the only file that includes a zlib header; without the define (node-gyp fallback) it uses Node's zlib.
29
+ 4. Emit `dist/bindings/cws_<platform>_<arch>_node<ABI>.node`.
30
+
31
+ ```sh
32
+ make # all four ABIs for the host OS/arch (needs curl, cmake, g++/clang)
33
+ ./build-on-docker.sh linux/arm64 # Linux arm64 (native on Apple Silicon, ~1 min)
34
+ ./build-on-docker.sh linux/amd64 # Linux x64 (default; emulated on Apple Silicon, slower)
35
+ make.bat # Windows x64; auto-detects VS 2022 (Build Tools or any edition)
36
+ ```
37
+
38
+ All of it can be driven from the Mac. Docker builds mount the repo and write straight into `dist/bindings/`. The Windows build runs in the Parallels "Windows 11" VM through the shared folder (cmd refuses UNC batch paths, hence `pushd`):
39
+
40
+ ```sh
41
+ prlctl resume "Windows 11"
42
+ prlctl exec "Windows 11" cmd /c "pushd \\\\Mac\\code\\cWS && make.bat" # 4 backslashes in zsh -> \\Mac
43
+ prlctl suspend "Windows 11"
44
+ ```
45
+
46
+ The VM is Windows on ARM: `vcvars64` still emits x64 code, so `make.bat` names outputs `win32_x64` explicitly, and testing them needs an x64 Node in the guest (an x64 zip from nodejs.org extracted under `C:\Temp` works; the guest's own ARM64 Node cannot load them). Docker base image is Ubuntu 22.04 = the minimum glibc baseline. Smoke-test a binding on its target Node with `node node_modules/mocha/bin/mocha -r ts-node/register 'tests/**/*.test.ts' --exit`, e.g. inside `node:22-bookworm` for Linux.
47
+
48
+ Adding a new Node major means: add a `VER_xxx`/ABI line to `Makefile` and `make.bat`, vendor that Node version's private headers into `src/headers/<major>/`, and add a `#if NODE_MAJOR_VERSION==<major>` include block in `src/Addon.h`. Vendoring = copy every `src/**/*.h` from the Node source tarball, plus `deps/ncrypto/ncrypto.h` and any `deps/v8/include/*.h` missing from the official headers tarball, then make `ssl_` public in `crypto/crypto_tls.h` (`TLSWrapSSLGetter` needs it). For 26 there is a second edit: `MaybeStackBuffer::AllocateSufficientStorage` (primary and the concept-constrained `V8Type` partial specialization) and `ToPath` are defined in-class in `util.h` instead of out-of-line in `util-inl.h`, because MSVC `cl.exe` cannot match out-of-line member definitions once that constrained specialization exists (Node builds Windows with clang-cl, which the Parallels VM does not have). Both edits are marked with a `cWS:` comment. Node's gyp-only config macros (e.g. `HAVE_SQLITE`, `HAVE_AMARO` for 26) must be defined before the include. Expect V8 API churn in `Addon.h` on each major; 26 needed tagged internal-field reads (`BaseObject::FromJSObject`) and dropped `Object::GetIsolate` and the `MakeCallback` overload without `async_context`.
49
+
50
+ ## Architecture
51
+
52
+ ### Two layers, one binary
53
+
54
+ - **`lib/*.ts` (thin JS facade)** compiled to `dist/`. `shared.ts` loads the native addon by filename `cws_${platform}_${arch}_node${versions.modules}` and wires native group callbacks (`onConnection`, `onMessage`, `onDisconnection`, …) to per-socket `registeredEvents`. Events are single-listener: `on()` overwrites, with a console warning. `client.ts` exports `WebSocket` (used for both client sockets and server-side peers, distinguished by `options.external`), `server.ts` exports `WebSocketServer` (aliased as `WebSocket.Server`).
55
+ - **`src/` (C++ addon)** is the uWebSockets 0.14 core (`Hub`, `Group`, `WebSocket`, `HTTPSocket`, `Networking`, `WebSocketProtocol.h`, `Extensions` for permessage-deflate) plus the V8 binding layer in `src/Addon.h` / `Addon.cpp`. `Backend.h` picks the event backend: libuv everywhere (`-DUSE_LIBUV` is always set), the epoll backend in `Epoll.*` is legacy and not compiled in by the Makefile.
56
+
57
+ ### How a server connection gets into C++ (the important part)
58
+
59
+ cWS does **not** run its own HTTP listener. `WebSocketServer` uses a Node `http`/`https` server (user-supplied or created internally) and hooks its `'upgrade'` event. In `upgradeConnection()`:
60
+
61
+ 1. It grabs the raw libuv handle from the Node socket (`socket._handle`, or `_parent._handle` for TLS).
62
+ 2. For TLS it calls `native.getSSLContext(socket.ssl)` to pull the raw `SSL*` out of Node's internal `TLSWrap`. This is why `src/headers/<major>/` vendors Node **private** headers (`crypto/crypto_tls.h`, `base_object-inl.h`, etc.) and `Addon.h` defines `NODE_WANT_INTERNALS` around them: `TLSWrapSSLGetter` subclasses `node::crypto::TLSWrap` to reach the protected `ssl_` member. This is the ABI-fragile part and the reason binaries are per-Node-major.
63
+ 3. `native.transfer(fd, ssl)` creates a `Ticket` and the Node socket is destroyed. On its `'close'` event, `native.upgrade(group, ticket, secKey, extensions, protocol)` hands the fd to the uWS `Hub`, which performs the WebSocket handshake and owns the socket from then on. On Linux with Node >= 20 the fd is `dup()`ed first as a workaround for io_uring (see comment in `Addon.h`).
64
+
65
+ `noServer: true` + `handleUpgrade()` follows the same path for `ws` compatibility. `path` and `verifyClient` checks happen in JS before the transfer.
66
+
67
+ ### Client connections
68
+
69
+ The client side never touches Node sockets: `native.connect(clientGroup, url, ws)` lets the uWS `Hub` do TCP/TLS itself using a process-wide `SSL_CTX` created in `cSNode.cpp` (TLS 1.0–1.2 only). `secureProtocol = 'TLSv1_2_method'` is exported for users to pin their https server to match.
70
+
71
+ ### Write corking
72
+
73
+ `Socket::sendTransformed` does not write when the socket is corkable (WebSockets only, plain TCP only) and `NodeData::corkState->enabled` is set. It frames the message into the socket's `messageQueue` and registers the socket in the per-loop `corkState->pending` list. `Socket::flushCorked` drains that list with one `sendmsg`/`WSASend` gather per socket from two libuv hooks registered in `registerCheck` (`src/Addon.h`): a `uv_prepare` (before the loop blocks in poll) and the existing `uv_check` (after poll and after Node's immediates). Anything that cannot be written falls through to the normal `UV_WRITABLE` drain loop in `ioHandler`. `closeSocket`/`onEnd`/`transfer` call `flushCorkedOnClose`, which unregisters the socket and pushes what it can to the kernel without invoking callbacks. `CWS_CORK=0` disables it at addon load (`corkEnabledFromEnv`). `HttpSocket` is never corkable because `Hub::upgrade` deletes it synchronously.
74
+
75
+ ### Things that are easy to get wrong
76
+
77
+ - `dist/*.js` and `dist/bindings/*.node` are committed. Rebuild TS with `npm run build-ts` and commit the output; binaries must be rebuilt on each platform when C++ changes.
78
+ - Binary messages reach JS as an `ArrayBuffer` that is a zero-copy view over the per-loop receive buffer shared by every socket. `onMessage` in `Addon.h` detaches it after the handler returns, so retaining it without `slice()` throws on later use rather than exposing another connection's bytes. Text frames are copied into a V8 string. Ping/pong payloads also go through the string path.
79
+ - Every handler, including `message`, is invoked through `node::MakeCallback`, which establishes an async-context scope and drains nextTick/microtasks after the handler returns. Because that drain happens before `onMessage` detaches the binary `ArrayBuffer`, a continuation that resolves within it still sees the bytes; anything that waits on real I/O sees a detached buffer.
80
+ - `shared.ts` currently has a leftover `console.log('log', ...)` printing the binding path on load.
81
+ - The addon exposes one `Group` per server plus a single shared client group; `ws.external` is the C++ `WebSocket*` and is nulled on disconnect, so every native call in `client.ts` guards on it.
82
+ - permessage-deflate: `Group` carries `deflateWindowBits`/`deflateMemLevel` (tier of the per-socket compressor allocated in the `WebSocket` ctor); the negotiator advertises `server_max_window_bits` when below 15 but ignores a smaller value requested by the client. The JS `send` decides `compress` per message: explicit option, else `compressThreshold` from the server config (undefined when deflate is off). Client-to-server inflate is one shared stream reset per message (`client_no_context_takeover` is always offered).
83
+ - Close code defaults to 1005 when the peer sends none (matches `ws`). App-level ping uses the single byte `'9'` (`APP_PING_CODE`) for browser clients that can't see protocol pings.
package/README.md CHANGED
@@ -12,9 +12,12 @@
12
12
 
13
13
  This table is true if you run ssl directly with `cws` (`Node.js`). In case if you use proxy for example `nginx`, `cws` can be run on bigger coverage.
14
14
 
15
- | cWS Version | Node 24 | Node 22 | Node 20 |
16
- |-------------|----------|---------|----------
17
- | 4.5.3 | X | X | X |
15
+ | cWS Version | Node 26 | Node 24 | Node 22 | Node 20 |
16
+ |-------------|---------|----------|---------|----------
17
+ | 4.8.0 | X | X | X | X |
18
+ | 4.7.0 | X | X | X | X |
19
+ | 4.6.0 | X | X | X | X |
20
+ | 4.5.3 | | X | X | X |
18
21
 
19
22
  ## Documentation
20
23
 
@@ -173,7 +176,14 @@ const wsServer = new WebSocket.Server({
173
176
  * noDelay?: boolean (set socket no delay)
174
177
  * noServer?: boolean (use this when upgrade done outside of cws)
175
178
  * maxPayload?: number
176
- * perMessageDeflate?: boolean | { serverNoContextTakeover: boolean;}
179
+ * perMessageDeflate?: boolean | { serverNoContextTakeover?: boolean; windowBits?: number; memLevel?: number; threshold?: number }
180
+ * true = shared compressor, no context takeover. serverNoContextTakeover: false = per-socket
181
+ * sliding window (streaming compression; far better on small messages). windowBits 9..15 and
182
+ * memLevel 1..9 pick its memory tier: 15/8 = ~256 KB per socket (default), 12/5 = ~32 KB, 10/3 = ~8 KB.
183
+ * threshold = minimum message size in bytes to compress (default 0); send(..., { compress }) overrides.
184
+ * level 1..9 (default 2): deflate level of the per-socket compressor. The prebuilt bindings use zlib-ng
185
+ * (see `zlibBackend` export): level 1 is its very fast "quick" strategy (~3-4x less CPU than zlib
186
+ * level 1 for ~8% worse ratio), level 2 matches zlib level 1's ratio.
177
187
  * verifyClient?: (info: ConnectionInfo, next: VerifyClientNext) => void (use to allow or decline connections)
178
188
  **/
179
189
  }, () => {
@@ -262,6 +272,12 @@ server.on('upgrade', (request, socket, head) => {
262
272
 
263
273
  **For more information check typings (`*.d.ts`) files in [dist](https://github.com/encharm/cWS/blob/master/dist) folder**
264
274
 
275
+ ### Write corking (performance)
276
+
277
+ All `send()` calls made to a socket during one event-loop iteration are framed immediately but written with a single gathered write (`writev`) when the iteration ends, instead of one syscall per `send()`. This is transparent to callers and preserves ordering; `send()` followed by `close()`/`terminate()` in the same tick still delivers the message. While writes are corked they are counted in `bufferedAmount` until the flush.
278
+
279
+ Set `CWS_CORK=0` in the environment to disable it and write every message immediately (previous behaviour). Corking applies to plain TCP sockets only; TLS sockets always write immediately.
280
+
265
281
  ### Secure WebSocket
266
282
  You can use `wss://` with `cws` by providing `https` server to `cws` and setting `secureProtocol` on https options:
267
283
 
package/binding.gyp CHANGED
@@ -12,8 +12,14 @@
12
12
  'src/cSNode.cpp',
13
13
  'src/WebSocket.cpp',
14
14
  'src/HTTPSocket.cpp',
15
- 'src/Socket.cpp'
15
+ 'src/Socket.cpp',
16
+ 'src/Zlib.cpp'
16
17
  ],
18
+ # Node private headers vendored per major (see src/headers/); the Makefile passes the same -I.
19
+ 'include_dirs': [
20
+ 'src/headers/<!(node -p "parseInt(process.versions.node)")'
21
+ ],
22
+ 'defines': ['HAVE_OPENSSL=1', 'NODE_WANT_INTERNALS=1'],
17
23
  'conditions': [
18
24
  ['OS=="linux"', {
19
25
  'cflags_cc': ['-std=c++17', '-DUSE_LIBUV'],
@@ -31,7 +37,7 @@
31
37
  }],
32
38
  ['OS=="mac"', {
33
39
  'xcode_settings': {
34
- 'MACOSX_DEPLOYMENT_TARGET': '10.7',
40
+ 'MACOSX_DEPLOYMENT_TARGET': '10.15',
35
41
  'CLANG_CXX_LANGUAGE_STANDARD': 'c++20',
36
42
  'CLANG_CXX_LIBRARY': 'libc++',
37
43
  'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
package/dist/client.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare class WebSocket {
12
12
  registeredEvents: any;
13
13
  private external;
14
14
  private socketType;
15
+ private compressThreshold;
15
16
  constructor(url: string, options?: any);
16
17
  get bufferedAmount(): number;
17
18
  get _socket(): SocketAddress;
package/dist/client.js CHANGED
@@ -4,6 +4,12 @@ exports.WebSocket = void 0;
4
4
  const server_1 = require("./server");
5
5
  const shared_1 = require("./shared");
6
6
  const clientGroup = shared_1.native.client.group.create(0, shared_1.DEFAULT_PAYLOAD_LIMIT);
7
+ function messageByteLength(message) {
8
+ if (typeof message === 'string') {
9
+ return Buffer.byteLength(message);
10
+ }
11
+ return message && typeof message.byteLength === 'number' ? message.byteLength : 0;
12
+ }
7
13
  shared_1.setupNative(clientGroup, 'client');
8
14
  class WebSocket {
9
15
  constructor(url, options = {}) {
@@ -23,6 +29,7 @@ class WebSocket {
23
29
  if (!this.url && this.options.external) {
24
30
  this.socketType = 'server';
25
31
  this.external = this.options.external;
32
+ this.compressThreshold = this.options.compressThreshold;
26
33
  }
27
34
  else {
28
35
  shared_1.native.connect(clientGroup, url, this);
@@ -76,7 +83,14 @@ class WebSocket {
76
83
  if (options && options.binary === true) {
77
84
  opCode = shared_1.OPCODE_BINARY;
78
85
  }
79
- shared_1.native[this.socketType].send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, options && options.compress);
86
+ let compress = false;
87
+ if (options && options.compress !== undefined) {
88
+ compress = !!options.compress;
89
+ }
90
+ else if (this.compressThreshold !== undefined) {
91
+ compress = messageByteLength(message) >= this.compressThreshold;
92
+ }
93
+ shared_1.native[this.socketType].send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
80
94
  }
81
95
  else if (cb) {
82
96
  cb(new Error('Socket not connected'));
package/dist/index.d.ts CHANGED
@@ -21,10 +21,15 @@ export declare type ServerConfigs = {
21
21
  noServer?: boolean;
22
22
  maxPayload?: number;
23
23
  perMessageDeflate?: boolean | {
24
- serverNoContextTakeover: boolean;
24
+ serverNoContextTakeover?: boolean;
25
+ windowBits?: number;
26
+ memLevel?: number;
27
+ level?: number;
28
+ threshold?: number;
25
29
  };
26
30
  verifyClient?: (info: ConnectionInfo, next: VerifyClientNext) => void;
27
31
  };
28
32
  export { WebSocket } from './client';
29
33
  export { WebSocketServer } from './server';
30
34
  export declare const secureProtocol: string;
35
+ export { zlibBackend } from './shared';
package/dist/index.js CHANGED
@@ -6,3 +6,5 @@ Object.defineProperty(exports, "WebSocket", { enumerable: true, get: function ()
6
6
  var server_1 = require("./server");
7
7
  Object.defineProperty(exports, "WebSocketServer", { enumerable: true, get: function () { return server_1.WebSocketServer; } });
8
8
  exports.secureProtocol = 'TLSv1_2_method';
9
+ var shared_1 = require("./shared");
10
+ Object.defineProperty(exports, "zlibBackend", { enumerable: true, get: function () { return shared_1.zlibBackend; } });
package/dist/server.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class WebSocketServer {
7
7
  private options;
8
8
  upgradeCb: (ws: WebSocket) => void;
9
9
  upgradeReq: HTTP.IncomingMessage;
10
+ compressThreshold: number | undefined;
10
11
  registeredEvents: any;
11
12
  private httpServer;
12
13
  private serverGroup;
package/dist/server.js CHANGED
@@ -12,13 +12,27 @@ class WebSocketServer {
12
12
  connection: shared_1.noop,
13
13
  };
14
14
  let nativeOptions = 0;
15
+ let windowBits = 15;
16
+ let memLevel = 8;
17
+ let level = 2;
15
18
  if (this.options.perMessageDeflate) {
16
19
  nativeOptions |= shared_1.PERMESSAGE_DEFLATE;
17
- if (this.options.perMessageDeflate.serverNoContextTakeover === false) {
20
+ const deflate = typeof this.options.perMessageDeflate === 'object' ? this.options.perMessageDeflate : {};
21
+ if (deflate.serverNoContextTakeover === false) {
18
22
  nativeOptions |= shared_1.SLIDING_DEFLATE_WINDOW;
19
23
  }
24
+ if (typeof deflate.windowBits === 'number') {
25
+ windowBits = deflate.windowBits;
26
+ }
27
+ if (typeof deflate.memLevel === 'number') {
28
+ memLevel = deflate.memLevel;
29
+ }
30
+ if (typeof deflate.level === 'number') {
31
+ level = deflate.level;
32
+ }
33
+ this.compressThreshold = typeof deflate.threshold === 'number' ? deflate.threshold : 0;
20
34
  }
21
- this.serverGroup = shared_1.native.server.group.create(nativeOptions, this.options.maxPayload || shared_1.DEFAULT_PAYLOAD_LIMIT);
35
+ this.serverGroup = shared_1.native.server.group.create(nativeOptions, this.options.maxPayload || shared_1.DEFAULT_PAYLOAD_LIMIT, windowBits, memLevel, level);
22
36
  shared_1.setupNative(this.serverGroup, 'server', this);
23
37
  if (this.options.noServer) {
24
38
  return;
package/dist/shared.d.ts CHANGED
@@ -9,4 +9,5 @@ export declare const PERMESSAGE_DEFLATE: number;
9
9
  export declare const SLIDING_DEFLATE_WINDOW: number;
10
10
  export declare const DEFAULT_PAYLOAD_LIMIT: number;
11
11
  export declare const native: any;
12
+ export declare const zlibBackend: string;
12
13
  export declare function setupNative(group: any, type: string, wsServer?: WebSocketServer): void;
package/dist/shared.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupNative = exports.native = exports.DEFAULT_PAYLOAD_LIMIT = exports.SLIDING_DEFLATE_WINDOW = exports.PERMESSAGE_DEFLATE = exports.APP_PING_CODE = exports.OPCODE_BINARY = exports.OPCODE_PING = exports.OPCODE_TEXT = exports.noop = void 0;
3
+ exports.setupNative = exports.zlibBackend = exports.native = exports.DEFAULT_PAYLOAD_LIMIT = exports.SLIDING_DEFLATE_WINDOW = exports.PERMESSAGE_DEFLATE = exports.APP_PING_CODE = exports.OPCODE_BINARY = exports.OPCODE_PING = exports.OPCODE_TEXT = exports.noop = void 0;
4
4
  const client_1 = require("./client");
5
5
  exports.noop = () => { };
6
6
  exports.OPCODE_TEXT = 1;
@@ -19,11 +19,12 @@ exports.native = (() => {
19
19
  throw err;
20
20
  }
21
21
  })();
22
+ exports.zlibBackend = exports.native.zlibBackend;
22
23
  function setupNative(group, type, wsServer) {
23
24
  exports.native.setNoop(exports.noop);
24
25
  exports.native[type].group.onConnection(group, (external) => {
25
26
  if (type === 'server' && wsServer) {
26
- const socket = new client_1.WebSocket(null, { external });
27
+ const socket = new client_1.WebSocket(null, { external, compressThreshold: wsServer.compressThreshold });
27
28
  exports.native.setUserData(external, socket);
28
29
  if (wsServer.upgradeCb) {
29
30
  wsServer.upgradeCb(socket);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@encharm/cws",
3
- "version": "4.5.3",
3
+ "version": "4.8.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "description": "cWS - fast C++ WebSocket implementation for Node.js",
package/src/Addon.cpp CHANGED
@@ -3,7 +3,7 @@
3
3
  #include "Addon.h"
4
4
 
5
5
  void Initialize(Local<Object> exports) {
6
- Isolate *isolate = exports->GetIsolate();
6
+ Isolate *isolate = Isolate::GetCurrent();
7
7
  exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "server").ToLocalChecked(),
8
8
  Namespace<cWS::SERVER>(isolate).object);
9
9
  exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "client").ToLocalChecked(),
@@ -20,6 +20,9 @@ void Initialize(Local<Object> exports) {
20
20
  NODE_SET_METHOD(exports, "upgrade", upgrade);
21
21
  NODE_SET_METHOD(exports, "connect", connect);
22
22
  NODE_SET_METHOD(exports, "setNoop", setNoop);
23
+ exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "zlibBackend").ToLocalChecked(),
24
+ String::NewFromUtf8(isolate, cWS::zlib::backend()).ToLocalChecked()).Check();
25
+ hub.getNodeData()->corkState->enabled = corkEnabledFromEnv();
23
26
  registerCheck(isolate);
24
27
  }
25
28
 
package/src/Addon.h CHANGED
@@ -24,6 +24,19 @@
24
24
  #include "headers/24/base_object-inl.h"
25
25
  #endif
26
26
 
27
+ #if NODE_MAJOR_VERSION==26
28
+ // Build-configuration macros normally supplied by Node's own gyp build;
29
+ // official binaries are built with both enabled.
30
+ #ifndef HAVE_SQLITE
31
+ #define HAVE_SQLITE 1
32
+ #endif
33
+ #ifndef HAVE_AMARO
34
+ #define HAVE_AMARO 1
35
+ #endif
36
+ #include "headers/26/crypto/crypto_tls.h"
37
+ #include "headers/26/base_object-inl.h"
38
+ #endif
39
+
27
40
  #include <tcp_wrap.h>
28
41
 
29
42
  using BaseObject = node::BaseObject;
@@ -64,8 +77,14 @@ using namespace v8;
64
77
 
65
78
  cWS::Hub hub(0, true);
66
79
  uv_check_t check;
80
+ uv_prepare_t corkPrepare;
67
81
  Persistent<Function> noop;
68
82
 
83
+ // Corked writes are flushed twice per loop iteration: in the prepare phase
84
+ // (right before the loop blocks in poll, so sends made from timers/nextTick/
85
+ // microtasks go out without waiting for I/O) and in the check phase (after
86
+ // poll and after Node's setImmediate callbacks, so sends made from I/O
87
+ // callbacks go out in the same iteration).
69
88
  void registerCheck(Isolate *isolate) {
70
89
  uv_check_init((uv_loop_t *)hub.getLoop(), &check);
71
90
  check.data = isolate;
@@ -73,9 +92,29 @@ void registerCheck(Isolate *isolate) {
73
92
  Isolate *isolate = (Isolate *)check->data;
74
93
  HandleScope hs(isolate);
75
94
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
76
- Local<Function>::New(isolate, noop), 0, nullptr);
95
+ Local<Function>::New(isolate, noop), 0, nullptr, node::async_context{0, 0});
96
+ cS::Socket::flushCorked(hub.getNodeData());
77
97
  });
78
98
  uv_unref((uv_handle_t *)&check);
99
+
100
+ uv_prepare_init((uv_loop_t *)hub.getLoop(), &corkPrepare);
101
+ uv_prepare_start(&corkPrepare, [](uv_prepare_t *) {
102
+ cS::Socket::flushCorked(hub.getNodeData());
103
+ });
104
+ uv_unref((uv_handle_t *)&corkPrepare);
105
+ }
106
+
107
+ // CWS_CORK=0 (or "false"/"off") disables write corking; anything else enables.
108
+ bool corkEnabledFromEnv() {
109
+ const char *value = getenv("CWS_CORK");
110
+ if (!value) {
111
+ return true;
112
+ }
113
+ std::string v(value);
114
+ for (char &c : v) {
115
+ c = (char) tolower((unsigned char) c);
116
+ }
117
+ return !(v == "0" || v == "false" || v == "off" || v == "no");
79
118
  }
80
119
 
81
120
  class NativeString {
@@ -102,7 +141,9 @@ class NativeString {
102
141
  #if NODE_MAJOR_VERSION >= 18
103
142
  length = arrayBufferView->ByteLength();
104
143
  auto store = arrayBufferView->Buffer()->GetBackingStore();
105
- data = reinterpret_cast<char*>(store->Data());
144
+ // A typed array may be a window into a larger ArrayBuffer (subarray);
145
+ // honour its offset or we would send bytes from the start of the pool.
146
+ data = reinterpret_cast<char*>(store->Data()) + arrayBufferView->ByteOffset();
106
147
  #else
107
148
  ArrayBuffer::Contents contents = arrayBufferView->Buffer()->GetContents();
108
149
  length = contents.ByteLength();
@@ -144,8 +185,17 @@ struct GroupData {
144
185
 
145
186
  template <bool isServer>
146
187
  void createGroup(const FunctionCallbackInfo<Value> &args) {
188
+ int windowBits = args.Length() > 2 && args[2]->IsNumber() ? (int) args[2].As<Integer>()->Value() : 15;
189
+ int memLevel = args.Length() > 3 && args[3]->IsNumber() ? (int) args[3].As<Integer>()->Value() : 8;
190
+ if (windowBits < 9) windowBits = 9;
191
+ if (windowBits > 15) windowBits = 15;
192
+ if (memLevel < 1) memLevel = 1;
193
+ if (memLevel > 9) memLevel = 9;
194
+ int level = args.Length() > 4 && args[4]->IsNumber() ? (int) args[4].As<Integer>()->Value() : 1;
195
+ if (level < 1) level = 1;
196
+ if (level > 9) level = 9;
147
197
  cWS::Group<isServer> *group = hub.createGroup<isServer>(
148
- args[0].As<Integer>()->Value(), args[1].As<Integer>()->Value());
198
+ args[0].As<Integer>()->Value(), args[1].As<Integer>()->Value(), windowBits, memLevel, level);
149
199
  group->setUserData(new GroupData);
150
200
  args.GetReturnValue().Set(External::New(args.GetIsolate(), group));
151
201
  }
@@ -271,7 +321,7 @@ void sendCallback(cWS::WebSocket<isServer> *webSocket, void *data,
271
321
  HandleScope hs(sc->isolate);
272
322
  node::MakeCallback(sc->isolate, sc->isolate->GetCurrentContext()->Global(),
273
323
  Local<Function>::New(sc->isolate, sc->jsCallback), 0,
274
- nullptr);
324
+ nullptr, node::async_context{0, 0});
275
325
  }
276
326
  sc->jsCallback.Reset();
277
327
  delete sc;
@@ -358,11 +408,15 @@ void transfer(const FunctionCallbackInfo<Value> &args) {
358
408
  Isolate* isolate = args.GetIsolate();
359
409
  Local<Context> context = isolate->GetCurrentContext();
360
410
  auto arg_0 = args[0]->ToObject(context).ToLocalChecked();
411
+ #if NODE_MAJOR_VERSION >= 26
412
+ // V8 14 tags internal-field pointers; Node's own reader knows the tag.
413
+ // The BaseObject* is the start of the TCPWrap (single inheritance chain).
414
+ void *handleWrap = node::BaseObject::FromJSObject(arg_0);
415
+ #else
361
416
  auto field_to_get = arg_0->InternalFieldCount() > 3 ? 1 : 0;
362
-
363
- uv_fileno((handle = getTcpHandle(
364
- arg_0->GetAlignedPointerFromInternalField(field_to_get))),
365
- (uv_os_fd_t *)&ticket->fd);
417
+ void *handleWrap = arg_0->GetAlignedPointerFromInternalField(field_to_get);
418
+ #endif
419
+ uv_fileno((handle = getTcpHandle(handleWrap)), (uv_os_fd_t *)&ticket->fd);
366
420
  } else {
367
421
  ticket->fd = args[0].As<Integer>()->Value();
368
422
  }
@@ -400,7 +454,7 @@ void onConnection(const FunctionCallbackInfo<Value> &args) {
400
454
  Local<Value> argv[] = {wrapSocket(webSocket, isolate)};
401
455
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
402
456
  Local<Function>::New(isolate, *connectionCallback),
403
- 1, argv);
457
+ 1, argv, node::async_context{0, 0});
404
458
  });
405
459
  }
406
460
 
@@ -424,8 +478,19 @@ void onMessage(const FunctionCallbackInfo<Value> &args) {
424
478
  HandleScope hs(isolate);
425
479
  Local<Value> argv[] = {wrapMessage(message, length, opCode, isolate),
426
480
  getDataV8(webSocket, isolate)};
427
- Local<Function>::New(isolate, *messageCallback)
428
- ->Call(isolate->GetCurrentContext(), Null(isolate), 2, argv);
481
+ node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
482
+ Local<Function>::New(isolate, *messageCallback), 2, argv, node::async_context{0, 0});
483
+ // Binary messages are zero-copy views over the shared per-loop receive
484
+ // buffer, which the next socket read overwrites. Detach the ArrayBuffer
485
+ // once the handler returns so a retained reference reads as empty and
486
+ // throws on use, instead of silently exposing another connection's data.
487
+ // Handlers that need the bytes after returning must copy them (slice()).
488
+ if (opCode == cWS::OpCode::BINARY) {
489
+ Local<ArrayBuffer> arrayBuffer = argv[0].template As<ArrayBuffer>();
490
+ if (arrayBuffer->IsDetachable()) {
491
+ arrayBuffer->Detach();
492
+ }
493
+ }
429
494
  }
430
495
  });
431
496
  }
@@ -446,7 +511,7 @@ void onPing(const FunctionCallbackInfo<Value> &args) {
446
511
  wrapMessage(message, length, cWS::OpCode::PING, isolate),
447
512
  getDataV8(webSocket, isolate)};
448
513
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
449
- Local<Function>::New(isolate, *pingCallback), 2, argv);
514
+ Local<Function>::New(isolate, *pingCallback), 2, argv, node::async_context{0, 0});
450
515
  });
451
516
  }
452
517
 
@@ -466,7 +531,7 @@ void onPong(const FunctionCallbackInfo<Value> &args) {
466
531
  wrapMessage(message, length, cWS::OpCode::PONG, isolate),
467
532
  getDataV8(webSocket, isolate)};
468
533
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
469
- Local<Function>::New(isolate, *pongCallback), 2, argv);
534
+ Local<Function>::New(isolate, *pongCallback), 2, argv, node::async_context{0, 0});
470
535
  });
471
536
  }
472
537
 
@@ -492,7 +557,7 @@ void onDisconnection(const FunctionCallbackInfo<Value> &args) {
492
557
  getDataV8(webSocket, isolate)};
493
558
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
494
559
  Local<Function>::New(isolate, *disconnectionCallback), 4,
495
- argv);
560
+ argv, node::async_context{0, 0});
496
561
  });
497
562
  }
498
563
 
@@ -510,7 +575,7 @@ void onError(const FunctionCallbackInfo<Value> &args) {
510
575
  Local<Value> argv[] = {
511
576
  Local<Value>::New(isolate, *(Persistent<Value> *)user)};
512
577
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
513
- Local<Function>::New(isolate, *errorCallback), 1, argv);
578
+ Local<Function>::New(isolate, *errorCallback), 1, argv, node::async_context{0, 0});
514
579
 
515
580
  ((Persistent<Value> *)user)->Reset();
516
581
  delete (Persistent<Value> *)user;
@@ -76,7 +76,7 @@ ExtensionsParser::ExtensionsParser(const char *data, size_t length) {
76
76
  }
77
77
 
78
78
  template <bool isServer>
79
- ExtensionsNegotiator<isServer>::ExtensionsNegotiator(int wantedOptions) {
79
+ ExtensionsNegotiator<isServer>::ExtensionsNegotiator(int wantedOptions, int serverMaxWindowBits) : serverMaxWindowBits(serverMaxWindowBits) {
80
80
  options = wantedOptions;
81
81
  }
82
82
 
@@ -100,6 +100,12 @@ std::string ExtensionsNegotiator<isServer>::generateOffer() {
100
100
  if (options & Options::SERVER_NO_CONTEXT_TAKEOVER) {
101
101
  //extensionsOffer += "; server_no_context_takeover";
102
102
  }
103
+
104
+ // Tell the peer our LZ77 window is smaller than the default so it can
105
+ // size its inflate window accordingly (RFC 7692 section 7.1.2.1).
106
+ if (isServer && serverMaxWindowBits < 15) {
107
+ extensionsOffer += "; server_max_window_bits=" + std::to_string(serverMaxWindowBits);
108
+ }
103
109
  }
104
110
 
105
111
  return extensionsOffer;
package/src/Extensions.h CHANGED
@@ -18,8 +18,9 @@ template <bool isServer>
18
18
  class ExtensionsNegotiator {
19
19
  protected:
20
20
  int options;
21
+ int serverMaxWindowBits;
21
22
  public:
22
- ExtensionsNegotiator(int wantedOptions);
23
+ ExtensionsNegotiator(int wantedOptions, int serverMaxWindowBits = 15);
23
24
  std::string generateOffer();
24
25
  void readOffer(std::string offer);
25
26
  int getNegotiatedOptions();
package/src/Group.cpp CHANGED
@@ -114,7 +114,7 @@ void Group<isServer>::removeWebSocket(WebSocket<isServer> *webSocket) {
114
114
  }
115
115
 
116
116
  template <bool isServer>
117
- Group<isServer>::Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData) : cS::NodeData(*nodeData), maxPayload(maxPayload), hub(hub), extensionOptions(extensionOptions) {
117
+ Group<isServer>::Group(int extensionOptions, unsigned int maxPayload, Hub *hub, cS::NodeData *nodeData, int deflateWindowBits, int deflateMemLevel, int deflateLevel) : cS::NodeData(*nodeData), maxPayload(maxPayload), hub(hub), extensionOptions(extensionOptions), deflateWindowBits(deflateWindowBits), deflateMemLevel(deflateMemLevel), deflateLevel(deflateLevel) {
118
118
  connectionHandler = [](WebSocket<isServer> *, HttpRequest) {};
119
119
  transferHandler = [](WebSocket<isServer> *) {};
120
120
  messageHandler = [](WebSocket<isServer> *, char *, size_t, OpCode) {};