node-tls-clientLatest version on npm

Quick start

Your first request, and the four calls every program makes.

Every program follows the same shape:

  1. initTLS() once, before any request. It loads the native library.
  2. new Session(options) with the browser profile to impersonate.
  3. session.get() / post() / … as many times as you like.
  4. session.close(), then destroyTLS() at shutdown.
app.mjs
import {
  initTLS,
  destroyTLS,
  Session,
  ClientIdentifier,
} from "node-tls-client";

await initTLS(); // loads (and on first run downloads) the native library

const session = new Session({
  clientIdentifier: ClientIdentifier.chrome_150,
  headers: {
    "user-agent":
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
  },
  timeout: 10_000, // ms, for every request of this session
});

try {
  // tls.peet.ws echoes back the fingerprint it saw
  const res = await session.get("https://tls.peet.ws/api/all");
  const seen = await res.json();
  console.log(res.status, res.usedProtocol); // 200 HTTP/2.0
  console.log(seen.tls.ja4); // t13d1516h2_8daaf6152771_…, Chrome's JA4
} finally {
  await session.close();
  await destroyTLS();
}

tls.peet.ws replies with the fingerprint it saw. Swap in your own URL once you've seen it work.

clientIdentifier, headers and timeout are only a few of the session's settings. Every option new Session() accepts is listed in SessionOptions; per-request options are in Request options.

Set the User-Agent yourself

A profile copies the browser's TLS and HTTP/2 handshake, not its headers. Without a headers option, every request is sent with User-Agent: tls-client/2.2.0, which no browser sends. Set a user-agent that matches the profile, as above. See Headers.

One-off requests

fetch() sends a single request in a throwaway session. It's handy when every request needs different settings:

// One request in a throwaway session: its own profile and timeout,
// closed as soon as the response arrives.
const res = await fetch("https://httpbin.org/post", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ hello: "world" }),
  options: { clientIdentifier: ClientIdentifier.firefox_148, timeout: 5000 },
});

const echoed = await res.json();
console.log(res.status, echoed.json); // 200 { hello: 'world' }

For several requests to the same site, prefer a Session. It keeps cookies and reuses connections; fetch() starts from scratch every time.

Things that surprise people

  • Redirects aren't followed unless you pass followRedirects: true. You get the 3xx response instead.
  • A failed request doesn't throw. DNS errors, TLS errors, proxy errors and timeouts resolve with status: 0, and body holds the reason. Check res.ok or res.status. See Errors and debugging.
  • Header values are arrays: res.headers["Content-Type"] is ["application/json"].
  • The timeout is per session (timeout, in milliseconds), not per request.

Next steps

On this page