npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ossiana/node-libcurl

v1.10.2

Published

A Node.js HTTP request library based on libcurl, with browser-grade TLS/HTTP2/HTTP3 fingerprint customization. It patches BoringSSL to customize the TLS client hello (cipher suites, extensions, order) and uses a custom HTTP/2 / HTTP/3 implementation, so t

Readme

node-libcurl

A Node.js HTTP request library based on libcurl, with browser-grade TLS/HTTP2/HTTP3 fingerprint customization. It patches BoringSSL to customize the TLS client hello (cipher suites, extensions, order) and uses a custom HTTP/2 / HTTP/3 implementation, so the traffic can be made to look like real Chrome or Firefox — something the built-in Node.js fetch can't do.


Build Status

| Platform | Support | | :-----------------: | :-----: | | Windows (x64) | Yes | | Ubuntu (x86_64) | Yes | | MacOS (x86_64/arm64) | Yes |

How to Install

npm i -g pnpm

pnpm i @ossiana/node-libcurl


Usage

The package exports four APIs:

| Export | Description | | :----- | :---------- | | requests | High-level, axios/requests-style API. Recommended. | | fetch | fetch-like API with a compatible response interface. | | LibCurl | Low-level wrapper around the native libcurl binding. | | LibCurlWebSocket | WebSocket client with fingerprint support. |

import { requests, fetch, LibCurl, LibCurlWebSocket } from "@ossiana/node-libcurl";

1. requests (axios-style)

Static requests (one-shot)

import { requests } from "@ossiana/node-libcurl";

const resp = await requests.get("https://httpbin.org/get", {
    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",
    },
    params: { page: 1, size: 20 }, // appended to query string
});
console.log(resp.status);   // 200
console.log(resp.text);     // response body as string
console.log(resp.json);     // response body parsed as JSON

Supported methods: get post put patch delete head options trace.

Session (persistent connection + cookies + retry)

A session reuses one LibCurl instance, so the TCP/TLS connection and cookies are kept between requests — pass it a shared instance to get the same behavior across sessions:

const session = requests.session({
    httpVersion: "http2",
    redirect: true,
    timeout: 15, // seconds
    // ja3: "chrome131",
    // akamai: "auto",
    // proxy: "http://user:[email protected]:8888",
});

const a = await session.get("https://example.com/login");
const b = await session.post("https://example.com/login", {
    json: { username: "user", password: "pass" }, // sets Content-Type: application/json
    // or use data for form-encoded bodies:
    // data: { username: "user", password: "pass" },
});

Session-only options:

const session = requests.session({
    defaultRequestHeaders: { // merged into every request of this session
        "User-Agent": "Mozilla/5.0 ... Chrome/150.0.0.0 Safari/537.36",
    },
    cookies: { // pre-set cookies for the given uri
        value: { name: "a", value: "b" },
        uri: "https://example.com",
    },
    autoSortRequestHeaders: "auto", // reorder headers like Chrome fetch
    requestType: "fetch",           // "fetch" | "XMLHttpRequest"
});

Session methods (in addition to the HTTP verbs):

session.setDefaultRequestHeaders(headers);
session.setCookie("key", "value", ".example.com", "/"); // set cookie manually
session.getCookie("key", ".example.com");               // get one cookie value
session.getCookies();                                   // "a=b; c=d;"
session.getCookiesMap();   // Map<string, { domain, subDomain, path, secure, timestamp, value }>
session.deleteCookie("key", ".example.com");
session.retry(3);          // returns a NEW session that retries up to 3 times
session.setProxy(proxy);
session.setTimeout(connectTime, sendTime); // seconds
session.setRedirect(true);
session.setHttpVersion("http2");
session.setInterface("eth0");
session.setJA3Fingerprint("chrome150");
session.setAkamaiFingerprint("auto");
session.setHttp3Fingerprint("auto");
session.getLastEffectiveUrl();

retry accepts a condition callback:

const retrySession = session.retry(3, (resp, error) => {
    // return true to stop retrying, false to retry again
    if (error) return false;
    return resp.status < 500;
});

Request options

| Option | Type | Description | | :----- | :--- | :---------- | | headers | string \| object \| string[] \| [string, string][] | Request headers. String form is "Key: value\nKey2: value2". | | params | URLSearchParams \| string \| object | Appended to the URL query string. | | json | object | Sends the object as JSON body, sets Content-Type: application/json. | | data | string \| Uint8Array \| URLSearchParams \| object | Sends a body. Objects are form-encoded (a=1&b=2), and Content-Type is set automatically. Cannot be combined with json. | | timeout | number | Timeout in seconds. | | redirect | boolean | Follow redirects (default false). | | proxy | string \| { proxy, username, password } | Proxy, e.g. "http://127.0.0.1:8888", "socks5://user:pass@host:1080", or an account object. For HTTP/3 use a SOCKS5 proxy with UDP relay — see HTTP/3 through a SOCKS5 UDP proxy. | | connectTo | string \| string[] | Connection replacement (CURLOPT_CONNECT_TO): when the request host is HOST:PORT, actually connect to CONNECT-TO-HOST:CONNECT-TO-PORT instead (DNS resolves the connect-to host, while Host/SNI keep the request host). Format: "HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT", e.g. "foo.abc.com:443:static.abc.com:443". | | httpVersion | "http1.1" \| "http2" \| "http3" \| "http3_only" | HTTP protocol version. | | interface | string | Bind to a specific network interface. | | ja3 | see Fingerprints | TLS (JA3) fingerprint. | | akamai | see Fingerprints | HTTP/2 Akamai fingerprint. | | http3Fingerprint | see Fingerprints | HTTP/3 (QUIC) fingerprint. | | autoSortRequestHeaders | "auto" \| "chrome130" \| "chrome131" \| boolean | Auto-sort request headers like Chrome fetch. | | tlsVerifySigalgs | string \| (string \| number)[] | Custom TLS signature algorithms. | | requestType | "fetch" \| "XMLHttpRequest" | HTTP/2 pseudo-header ordering style. | | headersOrder | string[] | Explicit order of request headers for the next request only. | | h2config | { weight: number, streamId?: number } | Customize the HTTP/2 stream weight / next stream id. | | sslCert | { certBlob, privateKeyBlob?, type?, password? } | Client certificate (type: "PEM" \| "DER" \| "P12"). | | sslVerify | { caPath: string } | Custom CA bundle path. |

HTTP/3 through a SOCKS5 UDP proxy

HTTP/3 runs over QUIC, which uses UDP instead of TCP — so a regular HTTP/HTTPS proxy (which relays TCP) cannot carry it. To proxy HTTP/3, use a SOCKS5 proxy that supports UDP relay (SOCKS5 UDP ASSOCIATE, RFC 1928 §7). When an HTTP/3 request is paired with a socks5:// proxy, the client automatically negotiates a UDP relay with the proxy and tunnels QUIC through it — no special scheme or extra option is needed:

const session = requests.session({
    httpVersion: "http3_only",           // or "http3"
    proxy: "socks5://user:pass@host:port", // a socks5 proxy with UDP relay
    http3Fingerprint: "auto",            // QUIC fingerprint still applies
});

const resp = await session.get("https://fp.impersonate.pro/api/http3");
console.log(resp.json.protocol);         // "http3"

The proxy object form and setProxy work too:

session.setProxy({
    proxy: "socks5://host:port",
    username: "user",
    password: "pass",
});

Same for fetch:

await fetch("https://example.com", {
    httpVersion: "http3_only",
    proxy: "socks5://user:pass@host:port",
});

Behavior notes:

  • http3_only fails hard (throws Couldn't connect to server) if the proxy has no working UDP relay.
  • http3 falls back to TCP (HTTP/1.1/2 through the proxy) when the UDP relay fails — the response will not be HTTP/3.
  • Very few proxy vendors support SOCKS5 UDP relay (most are HTTP/HTTPS-only or SOCKS5-TCP-only; those that support UDP typically ship it as a beta feature) — confirm with your provider before relying on it.

Response

| Property | Type | Description | | :------- | :--- | :---------- | | status | number | HTTP status code. | | text | string | Decoded response body. | | json | object | JSON.parse(text). | | buffer | Uint8Array | Raw response body. | | headers | string | Raw response headers. | | headersMap | Headers | Response headers as a Headers object. | | contentLength | number | Response body length. | | encodedBodySize | number | Wire-level (pre-decompression) body size. |

2. fetch (fetch-style)

import { fetch } from "@ossiana/node-libcurl";

const resp = await fetch("https://httpbin.org/post", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: { hello: "world" }, // object is JSON.stringify'd
    redirect: true,
    ja3: "chrome150",
    akamai: "auto",
    httpVersion: "http3",
});

await resp.text();          // Promise<string>
await resp.json();          // Promise<object>
await resp.arraybuffer();   // Promise<ArrayBuffer>
await resp.headers();       // Promise<Headers>
await resp.cookies();       // Promise<string>
await resp.cookiesMap();    // Promise<Map<string, {...}>>
await resp.lastEffectiveUrl(); // Promise<string>
resp.status();              // number (sync)
resp.contentLength();
resp.encodedBodySize();

Options (all optional): method (default "GET"), headers, body, redirect, cookies, httpVersion, verbose, proxy, connectTo, timeout, interface, instance, ja3, akamai, autoSortRequestHeaders, sslCert, sslVerify, tlsVerifySigalgs, http3Fingerprint (default "auto").

Pass a shared LibCurl instance via instance to keep a persistent connection across calls:

const curl = new LibCurl();
const r1 = await fetch("https://example.com/a", { instance: curl });
const r2 = await fetch("https://example.com/b", { instance: curl }); // reuses the connection

3. Fingerprints

This is the core feature — make your requests indistinguishable from a real browser.

JA3 (TLS fingerprint)

ja3: "auto"                       // pick by User-Agent Chrome version (default)
ja3: "chrome99" | "chrome101" | "chrome110" | "chrome124" | "chrome131" | "chrome133" | "chrome150" | "chrome152"
ja3: "771,4865-4866-4867-...,0-23-65281-10-11-35-16-...,29-23-24,0"  // custom JA3 string

The built-in versions randomize the TLS extension order per request (like real Chrome), and "auto" selects a preset matching the Chrome/x.y version in your User-Agent header.

Akamai (HTTP/2 fingerprint)

akamai: "auto"        // default
akamai: "chrome99" | "chrome107" | "chrome119"
akamai: "1:65536;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p"  // custom string

HTTP/3 fingerprint

http3Fingerprint: "auto"            // default
http3Fingerprint: "chrome126" | "chrome150" | "chrome152"
http3Fingerprint: {                 // fully custom config
    scid: "scid=0",
    settings: "1:65536;6:262144;7:100;51:1;GREASE",
    transport_params: "12584:0x4f524947;9:103;1:30000;7:6291456;15:AUTO;4:15728640;GREASE;32:65536;3:1472;17:1@1,GREASE;8:100;6:6291456;12583:174718;5:6291456",
    tls: "ciphers=1,2,3;alps=h3;grease=off;rand=on",
    permutation: "0,15,19,23,9,1,14,21,17,4,7,27",
    verify_sigalgs: "0x0403,0x0804,0x0401,0x0503,0x0805,0x0501,0x0806,0x0601,0x0201",
    trust_anchors: "44947.2.14,11129.9.8,...",  // optional: trust_anchors (0xCA34) IDs; empty = built-in Chrome 152 list
},

Proxied HTTP/3 needs a SOCKS5 proxy with UDP relay — see HTTP/3 through a SOCKS5 UDP proxy.

TLS signature algorithms (HTTP/1.1 HTTP/2)

tlsVerifySigalgs: [
    0x0403, "ecdsa_secp256r1_sha256", "rsa_pss_rsae_sha256", "rsa_pkcs1_sha256", ...
]

Auto-sorted request headers

With autoSortRequestHeaders: "auto" (default), request headers are automatically re-ordered the same way Chrome's fetch does (prefix / client-hint / middle / suffix groups). Use "chrome130" / "chrome131" to pin a specific ordering version.

4. LibCurl (low-level)

import { LibCurl } from "@ossiana/node-libcurl";

const curl = new LibCurl();
curl.open("POST", "https://example.com/api");
curl.setRequestHeaders({ "Content-Type": "application/json" });
curl.setJA3Fingerprint("chrome150");
curl.setAkamaiFingerprint("auto");
curl.setHttp3Fingerprint("auto");
curl.setProxy("127.0.0.1:8888");          // or { proxy, username, password }
curl.setConnectTo("foo.abc.com:443:static.abc.com:443"); // connect to static.abc.com instead
curl.setTimeout(10, 20);                  // connect / total, seconds
curl.setRedirect(true);
curl.setHttpVersion("http2");
curl.setInterface("eth0");
curl.setVerbose(true);                    // print curl internal logs
await curl.send({ hello: "world" });      // object is JSON.stringify'd

curl.getResponseStatus();       // number
curl.getResponseHeaders();      // string
curl.getResponseHeadersMap();   // Headers
curl.getResponseString();       // string
curl.getResponseBody();         // Uint8Array
curl.getResponseContentLength();
curl.getResponseEncodedBodySize();
curl.getCookies();              // "a=b; c=d;"
curl.getLastEffectiveUrl();

Other setters: setRequestHeader(key, value), setCookie({name, value, domain, path}), getCookie({name, domain, path}), getCookiesMap(), deleteCookie(), setSSLVerify({caPath}), setSSLCert(certBlob, privateKeyBlob?, "PEM"|"DER"|"P12", password?), setTLSVerifySigalgs(), setHttp2NextStreamId(streamId), setHttp2StreamWeight(weight), setAutoSortRequestHeaders(), setRequestType("fetch"|"XMLHttpRequest"), setNextRequestType(), setNextRequestHeadersOrder(order).

Note: one LibCurl instance can only run one request at a time — calling send() while a request is in flight throws.

5. WebSocket

const ws = new LibCurlWebSocket("wss://echo.websocket.org", {
    userAgent: "Mozilla/5.0 ... Chrome/150.0.0.0 Safari/537.36",
    origin: "https://example.com",
    cookie: "session=abc",
    protocol: "chat",
    timeout: 30,
    ja3: "chrome150",
    // instance: sharedCurl, // reuse an existing LibCurl
});

ws.onopen = () => {
    ws.send("hello");
    ws.send(new Uint8Array([1, 2, 3]));
};
ws.onmessage = (data: Uint8Array) => { /* receive */ };
ws.onclose = () => {};
ws.onerror = (message: string) => {};

ws.close();

Difference from Node.js fetch

  • The TLS fingerprint (JA3), HTTP/2 (Akamai) and HTTP/3 fingerprints can be customized to look like Chrome or Firefox — node-libcurl patches BoringSSL and ships a custom HTTP/2/3 stack, so the handshake, header ordering and cipher suites match real browsers.
  • Request headers can be auto-sorted exactly like Chrome's fetch (autoSortRequestHeaders).
  • Persistent connections and cookie jars are first-class (session API).
  • Client certificates, custom CA, per-interface binding, custom TLS signature algorithms, and HTTP/2 stream weight control are all supported.