<img src="https://github.com/Danny-Dasilva/CycleTLS/raw/main/docs/media/Banner.png" alt="CycleTLS"/>
Accepting Community Support and PR's
If you have a API change or feature request feel free to open an Issue
node ^v18.0
golang ^v1.21x
Node Js
$ npm install cycletls
Golang
$ go get github.com/Danny-Dasilva/CycleTLS/cycletls
You can run this test in tests/simple.test.ts
const initCycleTLS = require('cycletls');
// Typescript: import initCycleTLS from 'cycletls';
(async () => {
// Initiate CycleTLS
const cycleTLS = await initCycleTLS();
// Send request
const response = await cycleTLS('https://ja3er.com/json', {
body: '',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
proxy: 'http://username:password@hostname.com:443'
}, 'get');
// Parse response as JSON
const data = await response.json();
console.log(data);
// Cleanly exit CycleTLS
await cycleTLS.exit();
})();
Important: Pass
ja4rto configure the TLS ClientHello. JA4 (hash) is a report-only value; configuring with a JA4 hash will not change your fingerprint.
JA4R is the raw format of JA4 fingerprinting that allows explicit configuration of cipher suites, extensions, and signature algorithms:
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Chrome JA4R fingerprint (raw format)
const response = await cycleTLS('https://tls.peet.ws/api/all', {
ja4r: 't13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601'
});
const data = await response.json();
console.log('JA4:', data.tls.ja4);
console.log('JA4_r:', data.tls.ja4_r);
console.log('TLS Version:', data.tls.tls_version_negotiated);
await cycleTLS.exit();
})();
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init(cycletls.WithRawBytes())
defer client.Close()
// Chrome JA4R fingerprint (raw format)
response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
Ja4r: "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
}, "GET")
if err != nil {
log.Fatal(err)
}
log.Println("Response with JA4R:", response.Status)
}
HTTP/2 fingerprinting allows you to mimic specific browser HTTP/2 implementations:
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Firefox HTTP/2 fingerprint
const response = await cycleTLS('https://tls.peet.ws/api/all', {
http2Fingerprint: '1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0'
});
const data = await response.json();
console.log('HTTP/2 Fingerprint:', data.http2.akamai_fingerprint);
console.log('Settings:', data.http2.sent_frames[0].settings);
await cycleTLS.exit();
})();
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
// Firefox HTTP/2 fingerprint
response, err := client.Do("https://tls.peet.ws/api/all", cycletls.Options{
HTTP2Fingerprint: "1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0",
}, "GET")
if err != nil {
log.Fatal(err)
}
log.Println("Response with HTTP/2 fingerprint:", response.Status)
}
| Browser | HTTP/2 Fingerprint | Description |
|---|---|---|
| Firefox | 1:65536;2:0;4:131072;5:16384\|12517377\|0\|m,p,a,s |
Smaller window size, MPAS priority |
| Chrome | 1:65536;2:0;4:6291456;6:262144\|15663105\|0\|m,a,s,p |
Larger window size, MASP priority |
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Complete Chrome browser fingerprint with JA4R
const response = await cycleTLS('https://tls.peet.ws/api/all', {
ja4r: 't13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601',
http2Fingerprint: '1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0'
});
const data = await response.json();
console.log('Complete fingerprint applied successfully');
console.log('JA4:', data.tls.ja4);
console.log('HTTP/2:', data.http2.akamai_fingerprint);
await cycleTLS.exit();
})();
CycleTLS supports axios-compatible streaming responses for real-time data processing:
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
// Get streaming response
const response = await cycleTLS.get('https://httpbin.org/stream/3', {
headers: { Authorization: `Bearer your_token_here` },
responseType: 'stream',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0'
});
const stream = response.data;
stream.on('data', data => {
console.log('Received chunk:', data.toString());
});
stream.on('end', () => {
console.log("stream done");
await cycleTLS.exit();
});
stream.on('error', (error) => {
console.error('Stream error:', error);
await cycleTLS.exit();
});
})();
const initCycleTLS = require('cycletls');
(async () => {
const cycleTLS = await initCycleTLS();
try {
const response = await cycleTLS.get('https://httpbin.org/drip?numbytes=100&duration=2', {
responseType: 'stream',
ja3: '771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0',
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0',
});
console.log('Status:', response.status);
console.log('Headers:', response.headers);
const chunks = [];
response.data.on('data', (chunk) => {
chunks.push(chunk);
console.log(`Received ${chunk.length} bytes`);
});
response.data.on('end', () => {
console.log('Stream complete');
const fullData = Buffer.concat(chunks);
console.log('Total received:', fullData.length, 'bytes');
await cycleTLS.exit();
});
response.data.on('error', (error) => {
console.error('Stream error:', error);
await cycleTLS.exit();
});
} catch (error) {
console.error('Request failed:', error);
await cycleTLS.exit();
}
})();
For non-streaming responses, CycleTLS works exactly as before:
// These return buffered responses (existing behavior)
const jsonResponse = await cycleTLS.get('https://httpbin.org/json', {
responseType: 'json' // or omit for default JSON parsing
});
const jsonData = await jsonResponse.json();
console.log(jsonData); // Parsed JSON object
const textResponse = await cycleTLS.get('https://httpbin.org/html', {
responseType: 'text'
});
const textData = await textResponse.text();
console.log(textData); // String content
package main
import (
"log"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
client := cycletls.Init()
defer client.Close()
response, err := client.Do("https://ja3er.com/json", cycletls.Options{
Body: "",
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
EnableConnectionReuse: true, // Enable connection reuse for better performance
}, "GET")
if err != nil {
log.Print("Request Failed: " + err.Error())
}
log.Println(response)
}
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
http "github.com/Danny-Dasilva/fhttp" // note this is a drop-in replacement for net/http
)
func main() {
ja3 := "771,52393-52392-52244-52243-49195-49199-49196-49200-49171-49172-156-157-47-53-10,65281-0-23-35-13-5-18-16-30032-11-10,29-23-24,0"
ua := "Chrome Version 57.0.2987.110 (64-bit) Linux"
cycleClient := &http.Client{
Transport: cycletls.NewTransport(ja3, ua),
}
resp, err := cycleClient.Get("https://tls.peet.ws/")
...
}
The default Init() method provides the standard v1 API with chan Response. For performance-critical applications that can handle raw bytes, use the WithRawBytes() option:
package main
import (
"encoding/json"
"fmt"
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
func main() {
// Use WithRawBytes() option for performance enhancement
client := cycletls.Init(cycletls.WithRawBytes())
defer client.Close()
// Queue a request
go func() {
client.Queue("https://ja3er.com/json", cycletls.Options{
Ja3: "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-51-57-47-53-10,0-23-65281-10-11-35-16-5-51-43-13-45-28-21,29-23-24-25-256-257,0",
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:87.0) Gecko/20100101 Firefox/87.0",
}, "GET")
}()
// Performance pattern: receive raw bytes from RespChanV2
select {
case responseBytes := <-client.RespChanV2:
var response cycletls.Response
json.Unmarshal(responseBytes, &response)
fmt.Printf("Status: %d\n", response.Status)
fmt.Printf("Body: %s\n", response.Body)
// Alternative: still supports v1 pattern via RespChan
case response := <-client.RespChan:
fmt.Printf("Status: %d\n", response.Status)
fmt.Printf("Body: %s\n", response.Body)
}
}
Note: Use Init() for standard compatibility with chan Response. Use Init(cycletls.WithRawBytes()) when you need the performance benefits of handling raw []byte responses directly.
In order to create a cycleTLS instance, you can run the following:
// The initCycleTLS function spawns a Golang process that handles all requests concurrently via goroutine loops.
const initCycleTLS = require('cycletls');
// import initCycleTLS from 'cycletls';
// Async/Await method
const cycleTLS = await initCycleTLS();
// With optional configuration
const cycleTLS = await initCycleTLS({ port: 9118, timeout: 30000 });
// .then method
initCycleTLS().then((cycleTLS) => {});
import (
"github.com/Danny-Dasilva/CycleTLS/cycletls"
)
//The `Init` function initializes golang channels to process requests.
client := cycletls.Init()
The following methods exist in CycleTLS
**cyc
$ claude mcp add CycleTLS \
-- python -m otcore.mcp_server <graph>