@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.
- package/CHANGELOG.md +18 -0
- package/CLAUDE.md +83 -0
- package/README.md +20 -4
- package/binding.gyp +8 -2
- package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_arm64_node115.node +0 -0
- package/dist/bindings/cws_linux_arm64_node127.node +0 -0
- package/dist/bindings/cws_linux_arm64_node137.node +0 -0
- package/dist/bindings/cws_linux_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_x64_node115.node +0 -0
- package/dist/bindings/cws_linux_x64_node127.node +0 -0
- package/dist/bindings/cws_linux_x64_node137.node +0 -0
- package/dist/bindings/cws_linux_x64_node147.node +0 -0
- package/dist/bindings/cws_win32_x64_node115.node +0 -0
- package/dist/bindings/cws_win32_x64_node127.node +0 -0
- package/dist/bindings/cws_win32_x64_node137.node +0 -0
- package/dist/bindings/cws_win32_x64_node147.node +0 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +15 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +16 -2
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +3 -2
- package/package.json +1 -1
- package/src/Addon.cpp +4 -1
- package/src/Addon.h +80 -15
- package/src/Extensions.cpp +7 -1
- package/src/Extensions.h +2 -1
- package/src/Group.cpp +1 -1
- package/src/Group.h +4 -1
- package/src/HTTPSocket.cpp +1 -1
- package/src/Hub.cpp +11 -76
- package/src/Hub.h +12 -10
- package/src/Networking.h +11 -0
- package/src/Socket.h +201 -6
- package/src/WebSocket.cpp +8 -5
- package/src/Zlib.cpp +138 -0
- package/src/Zlib.h +38 -0
- package/src/cSNode.cpp +2 -0
- package/src/cSNode.h +4 -0
- package/src/headers/26/acorn_version.h +6 -0
- package/src/headers/26/aliased_buffer-inl.h +264 -0
- package/src/headers/26/aliased_buffer.h +205 -0
- package/src/headers/26/aliased_struct-inl.h +143 -0
- package/src/headers/26/aliased_struct.h +248 -0
- package/src/headers/26/amaro_version.h +6 -0
- package/src/headers/26/async_context_frame.h +33 -0
- package/src/headers/26/async_wrap-inl.h +123 -0
- package/src/headers/26/async_wrap.h +254 -0
- package/src/headers/26/base_object-inl.h +333 -0
- package/src/headers/26/base_object.h +339 -0
- package/src/headers/26/base_object_types.h +76 -0
- package/src/headers/26/blob_serializer_deserializer-inl.h +385 -0
- package/src/headers/26/blob_serializer_deserializer.h +142 -0
- package/src/headers/26/builtin_info.h +57 -0
- package/src/headers/26/callback_queue-inl.h +97 -0
- package/src/headers/26/callback_queue.h +79 -0
- package/src/headers/26/cares_wrap.h +475 -0
- package/src/headers/26/cleanup_queue-inl.h +36 -0
- package/src/headers/26/cleanup_queue.h +79 -0
- package/src/headers/26/compile_cache.h +134 -0
- package/src/headers/26/connect_wrap.h +26 -0
- package/src/headers/26/connection_wrap.h +30 -0
- package/src/headers/26/cppgc_helpers-inl.h +69 -0
- package/src/headers/26/cppgc_helpers.h +162 -0
- package/src/headers/26/crypto/crypto_aes.h +107 -0
- package/src/headers/26/crypto/crypto_argon2.h +88 -0
- package/src/headers/26/crypto/crypto_bio.h +193 -0
- package/src/headers/26/crypto/crypto_chacha20_poly1305.h +62 -0
- package/src/headers/26/crypto/crypto_cipher.h +291 -0
- package/src/headers/26/crypto/crypto_common.h +48 -0
- package/src/headers/26/crypto/crypto_context.h +219 -0
- package/src/headers/26/crypto/crypto_dh.h +104 -0
- package/src/headers/26/crypto/crypto_dsa.h +50 -0
- package/src/headers/26/crypto/crypto_ec.h +102 -0
- package/src/headers/26/crypto/crypto_hash.h +157 -0
- package/src/headers/26/crypto/crypto_hkdf.h +62 -0
- package/src/headers/26/crypto/crypto_hmac.h +92 -0
- package/src/headers/26/crypto/crypto_kem.h +131 -0
- package/src/headers/26/crypto/crypto_keygen.h +382 -0
- package/src/headers/26/crypto/crypto_keys.h +366 -0
- package/src/headers/26/crypto/crypto_kmac.h +79 -0
- package/src/headers/26/crypto/crypto_pbkdf2.h +76 -0
- package/src/headers/26/crypto/crypto_pqc.h +39 -0
- package/src/headers/26/crypto/crypto_random.h +127 -0
- package/src/headers/26/crypto/crypto_rsa.h +108 -0
- package/src/headers/26/crypto/crypto_scrypt.h +85 -0
- package/src/headers/26/crypto/crypto_sig.h +159 -0
- package/src/headers/26/crypto/crypto_spkac.h +21 -0
- package/src/headers/26/crypto/crypto_timing.h +20 -0
- package/src/headers/26/crypto/crypto_tls.h +342 -0
- package/src/headers/26/crypto/crypto_turboshake.h +105 -0
- package/src/headers/26/crypto/crypto_util.h +775 -0
- package/src/headers/26/crypto/crypto_x509.h +140 -0
- package/src/headers/26/dataqueue/queue.h +311 -0
- package/src/headers/26/debug_utils-inl.h +291 -0
- package/src/headers/26/debug_utils.h +200 -0
- package/src/headers/26/diagnosticfilename-inl.h +33 -0
- package/src/headers/26/embedded_data.h +17 -0
- package/src/headers/26/encoding_binding.h +59 -0
- package/src/headers/26/env-inl.h +956 -0
- package/src/headers/26/env.h +1294 -0
- package/src/headers/26/env_properties.h +562 -0
- package/src/headers/26/ffi/data.h +30 -0
- package/src/headers/26/ffi/fast.h +96 -0
- package/src/headers/26/ffi/jit_memory.h +26 -0
- package/src/headers/26/ffi/types.h +99 -0
- package/src/headers/26/handle_wrap.h +123 -0
- package/src/headers/26/histogram-inl.h +211 -0
- package/src/headers/26/histogram.h +474 -0
- package/src/headers/26/inspector/dom_storage_agent.h +68 -0
- package/src/headers/26/inspector/inspector_object_utils.h +38 -0
- package/src/headers/26/inspector/io_agent.h +30 -0
- package/src/headers/26/inspector/main_thread_interface.h +116 -0
- package/src/headers/26/inspector/network_agent.h +110 -0
- package/src/headers/26/inspector/network_inspector.h +45 -0
- package/src/headers/26/inspector/network_requests_buffer.h +195 -0
- package/src/headers/26/inspector/network_resource_manager.h +29 -0
- package/src/headers/26/inspector/node_json.h +24 -0
- package/src/headers/26/inspector/node_string.h +101 -0
- package/src/headers/26/inspector/notification_emitter.h +38 -0
- package/src/headers/26/inspector/protocol_helper.h +27 -0
- package/src/headers/26/inspector/runtime_agent.h +41 -0
- package/src/headers/26/inspector/storage_agent.h +33 -0
- package/src/headers/26/inspector/target_agent.h +59 -0
- package/src/headers/26/inspector/target_manager.h +70 -0
- package/src/headers/26/inspector/tracing_agent.h +43 -0
- package/src/headers/26/inspector/worker_agent.h +40 -0
- package/src/headers/26/inspector/worker_inspector.h +124 -0
- package/src/headers/26/inspector_agent.h +168 -0
- package/src/headers/26/inspector_io.h +78 -0
- package/src/headers/26/inspector_profiler.h +149 -0
- package/src/headers/26/inspector_socket.h +60 -0
- package/src/headers/26/inspector_socket_server.h +110 -0
- package/src/headers/26/js_native_api.h +634 -0
- package/src/headers/26/js_native_api_types.h +244 -0
- package/src/headers/26/js_native_api_v8.h +485 -0
- package/src/headers/26/js_native_api_v8_internals.h +45 -0
- package/src/headers/26/js_stream.h +57 -0
- package/src/headers/26/json_utils.h +172 -0
- package/src/headers/26/large_pages/node_large_page.h +34 -0
- package/src/headers/26/lru_cache-inl.h +69 -0
- package/src/headers/26/memory_tracker-inl.h +461 -0
- package/src/headers/26/memory_tracker.h +341 -0
- package/src/headers/26/module_wrap.h +231 -0
- package/src/headers/26/ncrypto.h +2008 -0
- package/src/headers/26/node.h +1693 -0
- package/src/headers/26/node_api.h +265 -0
- package/src/headers/26/node_api_internals.h +48 -0
- package/src/headers/26/node_api_types.h +58 -0
- package/src/headers/26/node_binding.h +170 -0
- package/src/headers/26/node_blob.h +160 -0
- package/src/headers/26/node_bob-inl.h +36 -0
- package/src/headers/26/node_bob.h +107 -0
- package/src/headers/26/node_buffer.h +92 -0
- package/src/headers/26/node_builtins.h +239 -0
- package/src/headers/26/node_concepts.h +46 -0
- package/src/headers/26/node_config_file.h +77 -0
- package/src/headers/26/node_constants.h +82 -0
- package/src/headers/26/node_context_data.h +171 -0
- package/src/headers/26/node_contextify.h +261 -0
- package/src/headers/26/node_crypto.h +66 -0
- package/src/headers/26/node_debug.h +30 -0
- package/src/headers/26/node_diagnostics_channel.h +121 -0
- package/src/headers/26/node_dir.h +52 -0
- package/src/headers/26/node_dotenv.h +45 -0
- package/src/headers/26/node_errors.h +378 -0
- package/src/headers/26/node_exit_code.h +54 -0
- package/src/headers/26/node_external_reference.h +171 -0
- package/src/headers/26/node_ffi.h +212 -0
- package/src/headers/26/node_file-inl.h +421 -0
- package/src/headers/26/node_file.h +561 -0
- package/src/headers/26/node_file_utils.h +39 -0
- package/src/headers/26/node_hash.h +212 -0
- package/src/headers/26/node_http2.h +1202 -0
- package/src/headers/26/node_http2_state.h +159 -0
- package/src/headers/26/node_http_common-inl.h +199 -0
- package/src/headers/26/node_http_common.h +535 -0
- package/src/headers/26/node_i18n.h +132 -0
- package/src/headers/26/node_internals.h +485 -0
- package/src/headers/26/node_locks.h +184 -0
- package/src/headers/26/node_main_instance.h +67 -0
- package/src/headers/26/node_mem-inl.h +113 -0
- package/src/headers/26/node_mem.h +45 -0
- package/src/headers/26/node_messaging.h +389 -0
- package/src/headers/26/node_metadata.h +172 -0
- package/src/headers/26/node_modules.h +102 -0
- package/src/headers/26/node_mutex.h +323 -0
- package/src/headers/26/node_object_wrap.h +145 -0
- package/src/headers/26/node_options-inl.h +524 -0
- package/src/headers/26/node_options.h +710 -0
- package/src/headers/26/node_perf.h +167 -0
- package/src/headers/26/node_perf_common.h +103 -0
- package/src/headers/26/node_platform.h +290 -0
- package/src/headers/26/node_process-inl.h +26 -0
- package/src/headers/26/node_process.h +112 -0
- package/src/headers/26/node_profiling.h +38 -0
- package/src/headers/26/node_realm-inl.h +156 -0
- package/src/headers/26/node_realm.h +236 -0
- package/src/headers/26/node_report.h +43 -0
- package/src/headers/26/node_revert.h +82 -0
- package/src/headers/26/node_root_certs.h +2937 -0
- package/src/headers/26/node_sea.h +101 -0
- package/src/headers/26/node_shadow_realm.h +46 -0
- package/src/headers/26/node_snapshot_builder.h +57 -0
- package/src/headers/26/node_snapshotable.h +167 -0
- package/src/headers/26/node_sockaddr-inl.h +248 -0
- package/src/headers/26/node_sockaddr.h +459 -0
- package/src/headers/26/node_sqlite.h +607 -0
- package/src/headers/26/node_stat_watcher.h +73 -0
- package/src/headers/26/node_task_runner.h +92 -0
- package/src/headers/26/node_threadsafe_cow-inl.h +54 -0
- package/src/headers/26/node_threadsafe_cow.h +105 -0
- package/src/headers/26/node_union_bytes.h +81 -0
- package/src/headers/26/node_url.h +101 -0
- package/src/headers/26/node_url_pattern.h +118 -0
- package/src/headers/26/node_v8.h +78 -0
- package/src/headers/26/node_v8_embedder.h +26 -0
- package/src/headers/26/node_v8_platform-inl.h +96 -0
- package/src/headers/26/node_version.h +116 -0
- package/src/headers/26/node_wasi.h +184 -0
- package/src/headers/26/node_wasm_web_api.h +55 -0
- package/src/headers/26/node_watchdog.h +164 -0
- package/src/headers/26/node_webstorage.h +62 -0
- package/src/headers/26/node_worker.h +162 -0
- package/src/headers/26/path.h +36 -0
- package/src/headers/26/permission/boolean_permission.h +50 -0
- package/src/headers/26/permission/fs_permission.h +192 -0
- package/src/headers/26/permission/permission.h +155 -0
- package/src/headers/26/permission/permission_base.h +79 -0
- package/src/headers/26/pipe_wrap.h +80 -0
- package/src/headers/26/quic/application.h +279 -0
- package/src/headers/26/quic/arena.h +373 -0
- package/src/headers/26/quic/bindingdata.h +440 -0
- package/src/headers/26/quic/cid.h +127 -0
- package/src/headers/26/quic/data.h +358 -0
- package/src/headers/26/quic/defs.h +417 -0
- package/src/headers/26/quic/endpoint.h +519 -0
- package/src/headers/26/quic/guard.h +5 -0
- package/src/headers/26/quic/http3.h +25 -0
- package/src/headers/26/quic/packet.h +166 -0
- package/src/headers/26/quic/preferredaddress.h +72 -0
- package/src/headers/26/quic/session.h +770 -0
- package/src/headers/26/quic/session_manager.h +107 -0
- package/src/headers/26/quic/sessionticket.h +110 -0
- package/src/headers/26/quic/streams.h +518 -0
- package/src/headers/26/quic/tlscontext.h +353 -0
- package/src/headers/26/quic/tokens.h +263 -0
- package/src/headers/26/quic/transportparams.h +184 -0
- package/src/headers/26/req_wrap-inl.h +175 -0
- package/src/headers/26/req_wrap.h +79 -0
- package/src/headers/26/spawn_sync.h +243 -0
- package/src/headers/26/stream_base-inl.h +170 -0
- package/src/headers/26/stream_base.h +487 -0
- package/src/headers/26/stream_pipe.h +76 -0
- package/src/headers/26/stream_wrap.h +137 -0
- package/src/headers/26/string_bytes.h +114 -0
- package/src/headers/26/string_decoder-inl.h +31 -0
- package/src/headers/26/string_decoder.h +49 -0
- package/src/headers/26/tcp_wrap.h +138 -0
- package/src/headers/26/threadpoolwork-inl.h +70 -0
- package/src/headers/26/timer_wrap-inl.h +32 -0
- package/src/headers/26/timer_wrap.h +87 -0
- package/src/headers/26/timers.h +70 -0
- package/src/headers/26/tracing/agent.h +125 -0
- package/src/headers/26/tracing/agent_legacy.h +146 -0
- package/src/headers/26/tracing/agent_perfetto.h +145 -0
- package/src/headers/26/tracing/node_trace_buffer.h +91 -0
- package/src/headers/26/tracing/node_trace_writer.h +79 -0
- package/src/headers/26/tracing/trace_event.h +27 -0
- package/src/headers/26/tracing/trace_event_helper.h +40 -0
- package/src/headers/26/tracing/trace_event_legacy.h +1113 -0
- package/src/headers/26/tracing/trace_event_legacy_inl.h +711 -0
- package/src/headers/26/tracing/trace_event_perfetto.h +47 -0
- package/src/headers/26/tracing/traced_value.h +167 -0
- package/src/headers/26/tty_wrap.h +65 -0
- package/src/headers/26/udp_wrap.h +231 -0
- package/src/headers/26/undici_version.h +6 -0
- package/src/headers/26/util-inl.h +788 -0
- package/src/headers/26/util.h +1207 -0
- package/src/headers/26/v8-cpp-heap-external.h +56 -0
- package/src/headers/26/v8-external-memory-accounter.h +60 -0
- package/src/headers/26/v8-fast-api-calls.h +784 -0
- package/src/headers/26/v8-inspector-protocol.h +13 -0
- package/src/headers/26/v8-inspector.h +458 -0
- package/src/headers/26/v8-metrics.h +273 -0
- package/src/headers/26/v8-trace-categories.h +28 -0
- package/src/headers/26/v8-unwinder-state.h +30 -0
- package/src/headers/26/v8-util.h +549 -0
- package/src/headers/26/v8-value-serializer-version.h +24 -0
- package/src/headers/26/v8-version-string.h +38 -0
- package/src/headers/26/v8-wasm-trap-handler-posix.h +31 -0
- package/src/headers/26/v8-wasm-trap-handler-win.h +28 -0
- package/src/headers/26/zlib_version.h +6 -0
- package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Info.plist +0 -20
- package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node115.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/Relocations/aarch64/cws_darwin_arm64_node115.node.yml +0 -931
- package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Info.plist +0 -20
- package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node127.node +0 -0
- 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.
|
|
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
|
|
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.
|
|
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',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/client.d.ts
CHANGED
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
|
-
|
|
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
|
|
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
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
|
-
|
|
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
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 =
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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;
|
package/src/Extensions.cpp
CHANGED
|
@@ -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) {};
|