Changelog
Every release and what changed, straight from CHANGELOG.md.
2.2.1 (November 12, 2026)
Behavior changes: upgrading downloads the native library again (the cached copy is named after the package version), and the newest is now tls-client 1.18.1, for both binaries. Previously the default binary was 1.16.0 and binary: "node-tls-client" was 1.18.0. What you'll notice:
- The default profile's fingerprint changes.
chrome_150andchrome_150_psknow send the trust-anchors extension (51764), as Chrome 150 does. JA4 goes fromt13d1516h2_8daaf6152771_806a8c22fdeatot13d1517h2_8daaf6152771_cb7bf5808d99, the same aschrome_152. - Characters a URL can't carry raw are percent-encoded in query strings, as browsers do:
?scope=openid offline_accessis sent as?scope=openid%20offline_access. Before, they were sent raw, and servers usually answered 400. - With the default binary,
disableSessionTicketsandtrustAnchorsPayloadnow take effect; 1.16.0 ignored them.
Additions
- New client profiles:
chrome_154,chrome_154_pskandsafari_27_2(desktop Safari 27.2).chrome_154(_psk)ischrome_152(_psk)with the trust-anchors extension sorted, as Chrome 154 and later send it, so its bytes no longer change from one process to the next; pair it with a current ChromeUser-Agent. A native library older than 1.18.1 doesn't know these names and uses its default profile (chrome_150) instead, without an error. This can happen wheninitTLS()warns that it's using a previously cached library.
2.2.0 (November 5, 2026)
Behavior changes (no API removals):
- New
binaryoption for the native library. The default is unchanged:"upstream", tls-client's official release.binary: "node-tls-client"opts into our build, which runs requests differently: every request you start runs at once instead of waiting for a free thread, so memory grows with requests in flight,UV_THREADPOOL_SIZEno longer limits them, andconcurrencyis ignored with a warning. It is built from tls-client 1.18.0 (the official release is 1.16.0) and runs this way on the main thread only: in a worker thread, or when our build isn't available for your platform or can't be downloaded,initTLS()warns and uses the official binary. - Requests run on Node's libuv threadpool instead of 2.1.0's worker threads. Importing the package sets
UV_THREADPOOL_SIZEto CPU cores × 2 when it's unset, which also sizes the pool your ownfs,dnsandcryptocalls use; set it yourself to choose the size. See Concurrency. - Default client profile is now
chrome_150(previouslychrome_131), matching native's default. Applies only to sessions with no explicitclientIdentifierorja3string. PinclientIdentifier: ClientIdentifier.chrome_131to keep the old fingerprint. - Per-request
headersnow merge over sessionheaders(#14): case-insensitive, the request's value and casing win,null/undefinedremoves a session header. Previously any per-requestheadersobject replaced the whole session set — silently dropping the sessionUser-Agent, so native sentGo-http-client/1.1. This matches axios, got and Python requests; to send a request without a session header, set that key tonull. connectHeaders: the request-level value now wins over the session-level one (previously inverted).bodymust be a string orBuffer/Uint8Array(sent base64-encoded as a byte request). Objects now throwTlsClientErrorinstead of failing natively withstatus: 0.- Idle connections: a session now keeps up to 256 idle HTTP/1.1 connections per host, as Node's
http.Agent(and so node-fetch) does. Native's previous default of 2 made parallel requests to one host open and close a connection each, which could run out of local ports (EADDRNOTAVAIL). SettransportOptions.maxIdleConnsPerHostto change it (0restores the old default).
Performance
Medians of three runs on an Apple M5 (10 cores) with Node.js 26.7.0, against a local server that answers in 50 ms. Charts, all results and the method are on the Performance page; the harness is in bench/.
| 2.1.0 | 2.2.0 | 2.2.0 + binary: "node-tls-client" | node-fetch 2.7.0 | |
|---|---|---|---|---|
| 64 in flight: requests/s | 377 | 385 | 1,207 | 1,228 |
| 1,024 in flight: requests/s | 383 | 384 | 15,432 | 14,339 |
| 5,000 at once: time to finish | 13.1 s | 13.0 s | 0.26 s | 0.43 s |
| 2,000 HTTPS (HTTP/1.1) at once: time to finish | 5.21 s | 5.20 s | 0.47 s | 8.08 s ¹ |
| 2,000 HTTPS (HTTP/2) at once: time to finish | 5.26 s | 5.21 s | 0.10 s | not supported |
| 16 in flight: CPU per request | 9.83 ms | 0.56 ms | 0.50 ms | 0.42 ms |
| 64 in flight: CPU per request | 1.33 ms | 0.49 ms | 0.31 ms | 0.23 ms |
| 64 in flight: peak memory | 372 MB | 86 MB | 89 MB | 84 MB |
¹ node-fetch lost connections in two of three runs (401 of 6,000 requests failed with ETIMEDOUT): the test server's queue of waiting connections holds 128 on macOS by default. Its clean run took 0.8 s.
2.1.0 and 2.2.0 run CPU cores × 2 requests at once (20 here), which caps them near 385 requests/s against this server; 2.2.0 can go higher with UV_THREADPOOL_SIZE. The node-tls-client binary has no such cap.
Transport rewrite
- Piscina worker pool removed (#11). Native calls now use koffi's asynchronous calls on the libuv threadpool, throttled by an in-process semaphore, with an unbounded FIFO queue for bursts. This avoids the
max_async_callslimit that broke the first attempt: koffi ≥ 2.15.2 is required and its limit is raised to 4096. - Dependencies:
piscinaandcookie-parserare no longer installed; the runtime dependencies arekoffi(≥ 2.15.2) andtough-cookie. initTLS({ concurrency })caps in-flight native calls (admission depth). Real parallelism is the libuv threadpool: the library setsUV_THREADPOOL_SIZEto cores×2 at import when unset (libuv caps it at 1024; setUV_THREADPOOL_SIZE=4to keep Node's default). Each in-flight call reserves ~256 KiB in koffi.- The
UV_THREADPOOL_SIZEdefault does not apply to an ES module entry file on Node 20, Node 22 before 22.18 and Node 24 before 24.3: Node starts its threadpool before this package loads, so at most 4 requests run in parallel. A one-time warning frominitTLS()says so (not withbinary: "node-tls-client", which doesn't use the threadpool for requests); setUV_THREADPOOL_SIZEin the environment (UV_THREADPOOL_SIZE=20 node app.mjs) there. CommonJS entry files and newer Node versions are unaffected. destroyTLS()rejects new requests, waits for in-flight ones (each bounded by its own timeout), then clears native state.- koffi keeps as many async memory pools resident as requests are expected in flight (up to its maximum of 16), so steady-state requests reuse pools instead of mapping 256 KiB each.
Additions
- Faster native binary, opt-in:
initTLS({ binary: "node-tls-client" })downloads our build of tls-client (Sahil1337/tls-client), made from upstream's newest version tag, sodisableSessionTicketsandtrustAnchorsPayloadwork with it. It adds a completion queue: a request no longer holds a thread while it waits for the server, and the library calls back when responses arrive. Against a server that answers in 50 ms, 5,000 requests at once finish in 0.26 s instead of 13.0 s, and 64 at a time reach 1,207 requests/s instead of 385 (node-fetch: 1,228); see Performance. Memory peaks at 349 MB with 5,000 at once (node-fetch: 278 MB); as with node-fetch there is no built-in cap, so limit requests in flight in your own code if memory matters. - New client profiles:
chrome_130_psk,chrome_133(_psk),chrome_144(_psk),chrome_146(_psk),chrome_150(_psk),chrome_152(_psk),brave_146(_psk),safari_ios_18_5,safari_ios_26_0,firefox_135,firefox_146_psk,firefox_147(_psk),firefox_148. - New SessionOptions:
disableHttp3,withProtocolRacing,withoutCookieJar,withCustomCookieJar,certificatePinningHosts,timeoutSeconds,disableSessionTickets. - New custom-client (ja3string) options:
h3Settings,h3SettingsOrder,h3PseudoHeaderOrder,h3PriorityParam,h3SendGreaseFrames,echCandidatePayloads,echCandidateCipherSuites,recordSizeLimit,streamId,allowHttp,supportedDelegatedCredentialsAlgorithms,trustAnchorsPayload(required when the ja3 string lists extension 51764). - New enum values:
KeyShareCurvesgainsX25519Kyber768Old,X25519MLKEM768;SupportedSignatureAlgorithmsgainsSHA224_RSA,SHA224_ECDSA,MLDSA44,MLDSA65,MLDSA87,GREASE. - Custom install directory for the native library:
initTLS({ libraryDir: "./vendor/tls" })downloads the shared library to, and loads it from, that directory instead ofos.tmpdir(). The directory is created when missing and relative paths resolve against the current working directory. Use it to install the binary ahead of time (e.g. in a Docker build step) or to copy it onto an offline machine. Leaving it out keeps theos.tmpdir()default used since 1.1.1. TlsClientErroris exported from the package root, so the library's own errors (e.g. frominitTLS(), or anythingfetch()rejects with) can be checked withinstanceof. The deep importnode-tls-client/dist/utils/tlsErrorkeeps working.HeaderInputtype for allheadersoptions (numbers, arrays andnullaccepted).- Linux ppc64le, s390x and riscv64 are supported.
- HTTP/3 guide: HTTP/3 (example: examples/advanced/http3.mjs)
- New documentation site: sahil1337.github.io/node-tls-client, with a guide per feature and runnable examples in
examples/. Old API reference URLs redirect to the new pages; the readme is now a short intro.
Deprecations
certCompressionAlgo(string) →certCompressionAlgos(array). The old singular key still works (wrapped into an array) but will be removed in a future major. Default remains["zlib"].IClienttype → useTlsNativeLibrary(the bindings are now promise-returning).
Fixes
- A failed native download no longer exits or crashes the process (#16).
initTLS()now throws aTlsClientErrorinstead of callingprocess.exit(1), a TLS error during the download (e.g.ERR_SSL_PACKET_LENGTH_TOO_LONG) rejectsinitTLS()instead of surfacing as an uncaught exception, and a stalled download fails after 30 s instead of hanging. - The native download handles both upstream asset naming schemes. tls-client 1.16.0 was briefly published with only renamed assets (
tls-client-xgo-<version>-<os>-<arch>, September 2–7, 2026), which made fresh 2.1.0 installs exit duringinitTLS(). - The cached native binary refreshes on upgrade, so new profiles and options work right after updating. If that download fails, a previously cached binary is used with a warning.
- Memory leak on
session.close()/fetch(): native memory for each closed session was never freed. timeoutandtimeoutSecondstogether were a hard native error; the wrapper now sends onlytimeout(ms) and warns once.timeout: 0means unset.headerPriority: nullomits the field on the ja3 path (native then applies no priority); leaving it unset still sends the zeroed priority earlier versions sent.H2Settings.ENABLE_PUSHset to a boolean, as 2.1.0 typed it, failed every request natively (status: 0, "cannot unmarshal bool"); booleans are now sent as1/0, and numbers are accepted too.headerOrderacceptsstring[].SessionOptions.sessionIdis honored (it was documented but always replaced by a random UUID); twoSessionobjects with the same id share one native session.- Per-request
cookiesvalues are sent verbatim; a value containing;was previously cut at the semicolon. - A response cookie that cannot be parsed no longer fails an otherwise successful request.
H2SettingsacceptsUNKNOWN_SETTING_7,UNKNOWN_SETTING_8andUNKNOWN_SETTING_9, and everyTransportOptionsfield is optional (native treats a missing field as its default).
Notes
- Cookies are kept between requests by the native session jar (
withoutCookieJarturns it off);session.cookies()reports the cookies the session has seen. withProtocolRacingwith a non-SOCKS5 proxy is rejected by native 1.16.0 (status: 0).disableSessionTicketsandtrustAnchorsPayloadneed a tls-client build newer than the 1.16.0 release binaries, which predate those fields and silently ignore them.- Alpine (musl) is not supported yet: upstream only publishes an amd64 Alpine build, and this version does not download it. Use a glibc-based image such as
node:<version>-slim. - HTTP/3 known issues (native 1.16.0, protocol racing only): only
chrome_144(_psk),firefox_147(_psk)andfirefox_148carry real HTTP/3 settings, and heavy concurrent racing can crash the process. UsedisableHttp3: truefor volume. - With upstream's binary, native calls share the libuv threadpool with Node's
fs/DNS/zlib/crypto work; sizeUV_THREADPOOL_SIZEfor both. - Requires Node.js ≥ 20.
initTLS()downloads the newest upstream release intoos.tmpdir()(orlibraryDir) on first use and after a wrapper upgrade. Responseheadersvalues arestring[]at runtime.- The internal file layout changed (
dist/lib,dist/interfaceanddist/workersare gone). Import from the package root, which exports the whole public API. Deep imports intodist/were never part of the API;dist/utils/tlsErroris kept for existinginstanceofchecks.
2.1.0 (June 21, 2025)
This version contains breaking changes.
Breaking changes
- TLS Client Initialization Requirement
The new methodinitTLS()must be called before using any session methods.
You must also calldestroyTLS()when the session is no longer needed. - Refactor Cookies API
<session>.cookiesis no longer a synchronous getter.
It now returns aPromisethat resolves to a cookie object.
Usage: Cookies - Redirect Handling Change
Theredirectoption inrequestOptionsis no longer supported. UsefollowRedirectsinstead.
Additions/Fixes
-
Node.js Compatibility Fix
Theprocess.clearLine()bug has been resolved. Fixed issues #2 , #3 , #6 -
Shared Worker Pool (Performance Improvement)
A centralized shared worker pool has been introduced, leading to major performance gains across concurrent TLS sessions.Proof of concept:
-
New:
transportOptionsin SessionOptions
IntroducedtransportOptionsto customize low-level transport configuration.
Docs → transportOptions -
Additional Session Options Introduced
The following options provide extended control over stream output:serverNameOverwritestreamOutputBlockSizestreamOutputEOFSymbolstreamOutputPath
Docs → SessionOptions
-
Enhanced Documentation
Executable compatibility instructions and examples have been added to the documentation. Instructions -
Refactor byteRequest handling Taking into account the
Content-EncodingandContent-Transfer-Encodingheaders to determine if the response is a byte response. Accounting for pull request #8
2.0.0 (December 15, 2024)
This version contains breaking changes.
- Call
<session>.init()after setting up a session and before using any HTTP methods. (See the documentation for more details.) - Resolved memory leak issues by moving to workerpool.
- Added new profiles:
chrome_131,chrome_131_psk,firefox_132,firefox_133, andios_safari_18. The default profile is nowchrome_131(previouslychrome_124). - Introduced the
isRotatingProxyoption for handling rotating proxies. PS: SessionOptions
1.1.4 (July 29, 2024)
This version includes significant updates and improvements.
Breaking changes
response.cookiesis now a property instead of a method. [Note:response.cookies()has been removed and replaced withresponse.cookies.]
Additional updates
- Added
session.cookiesproperty to retrieve all session cookies. For detailed information, see Cookies. - Added
byteResponseoption in requestOptions. Setting this to true ensures that the response is treated as a byte response. For detailed information, see Responses → Binary downloads - Added functionality to send binary data. Note: Ensure that the appropriate headers are set before sending binary data in the body.
- Added
disableIPV6anddisableIPV4options inSessionOptions. For detailed information, see SessionOptions - Added
hostOverrideproperty in requestOptions. Used to override the Host header, typically needed when making requests directly to an IP address. chrome_124profile will be used when noclient_identifieris specified in session options.- Refactored cookies.ts and session typings for improved readability (internal change, no impact on existing use cases)
- Updated TlsResponse according to new changes.
1.1.3 (June 13, 2024)
This version contains breaking changes.
Breaking changes
response.json()andresponse.text()methods now return aPromise.response.cookies()now returns an object of cookies instead of aPromise.session.methodsno longer include thetimeoutandrejectUnauthorizedoptions. Since timeout cannot be changed during a session, ensure to specifytimeout&rejectUnauthorizedin SessionOptions when creating a session.
Additions
fetch(fetchOptions)method have been added to use different timeouts & rejectUnauthorized values in each request. See fetch() for more information.
Fixes
- Fixed a typo that prevented timeout from working correctly.
1.1.2 (June 9, 2024)
Additions
- Introduced client profile types.
Changes
- Enhanced documentation for improved clarity and usability.
- Updated the README with minor adjustments.
- Refined interface definitions for better readability and understanding.
1.1.1 (June 4 2024)
Additions
- Implemented dynamic methods for downloading shared files in "node-tls-client", eliminating the inclusion of prebuilt files.
Changes
- Path used for storing the shared library files is strictly set to tmpdir path.
Benefits
- Reduces package size.
- Ensures better support for executables.
1.1.0 (June 3, 2024)
Changes
- Improved handling of shared library files:
- Only the shared library file matching the current operating system is preserved. All other shared library files are deleted.
- Added support for executables.