@cldmv/stubborn-tcp
v1.0.4
Published
A stubbornly persistent TCP client that automatically reconnects when connections fail
Maintainers
Readme
@cldmv/stubborn-tcp
@cldmv/stubborn-tcp is a TCP and TLS client for Node.js that refuses to stay disconnected. When the connection drops, it reconnects on its own with exponential backoff, and it can keep a quiet link alive with TCP keep-alive and application-level heartbeats.
It is built for long-lived connections to devices and services — control protocols, telemetry feeds, line-based servers — where the remote end restarts, the network blips, and the client is expected to simply carry on. Everything is exposed through an EventEmitter, with legacy callback options kept for code ported from older clients.
A stubbornly persistent TCP/TLS client that automatically reconnects when connections fail.
✨ What's New
Latest: v1.0.4 (October 2026)
- TLS connections work — the
tlsoption shadowed Node.js'stlsmodule inside the constructor, so every TLS connection attempt threwtls.connect is not a function.tls: truenow connects over TLS, andsecureConnectand the TLS helper methods work as documented (#1). - Built package with TypeScript declarations — the code now ships as a minified
dist/bundle with.d.mtsdeclarations;test/andexamples/are no longer published. The package root exports are unchanged, andrequire()(Node.js ^20.19.0 or >=22.12.0) now works in esbuild/webpack bundles and fails with a clearERR_REQUIRE_ESMmessage on older Node.js (#1). - Connection fixes — an unobserved socket error no longer crashes the process,
failToConnectnow fires for each failed attempt,open()on a connected client keeps it usable,open()without arguments uses the constructor's host and port, andconnectionTimeoutno longer drops idle connections (a new opt-inidleTimeoutcovers that) (#9, #10, #11, #13). - View full v1.0.4 Changelog
Recent Releases
- v1.0.3 (October 2025) —
debug: trueemitsdebugevents instead of printing to the console; zero or negative timeouts disable the socket timeout (Changelog) - v1.0.2 (October 2025) —
dataevent arguments become(data, handle, instance)(Changelog) - v1.0.1 (October 2025) —
require()returns the constructor instead of a Promise (Changelog) - v1.0.0 (October 2025) — Initial release: auto-reconnecting TCP client with backoff, socket tuning and heartbeats (Changelog)
📚 For complete version history and detailed release notes, see the docs/changelog/ folder.
🚀 Key Features
- 🔄 Auto-reconnection: Intelligent exponential backoff reconnection strategy
- 🔒 TLS/SSL Support: Full TLS encryption with certificate validation
- 🚀 Socket Optimization: TCP keep-alive, no-delay, and timeout configuration
- 💓 Application Heartbeat: Cycling heartbeat functions with activity-based reset
- 🎯 Modern API: Options-based constructor with EventEmitter support
- 📦 Dual Module Support: Works with both ESM (
import) and CommonJS (require) - 🛡️ Production Ready: Comprehensive error handling and connection monitoring
- 🔧 Highly Configurable: Fine-grained control over all connection parameters
- 📝 Well Documented: Comprehensive JSDoc documentation and TypeScript declarations
📦 Installation
Requirements
- Node.js 14.0 or higher for
import(ESM). require()needs Node.js ^20.19.0 or >=22.12.0 — the CommonJS entry loads the ES module build throughrequire(esm), and throwsERR_REQUIRE_ESMwith a pointer toimport()on older versions.- Platforms: Windows, macOS, Linux.
- Protocol Support: TCP and TLS/SSL.
Install
npm install @cldmv/stubborn-tcp🚀 Quick Start
Passing host and port to the constructor connects immediately. Omit them and call open(host, port) when you want to connect later.
Basic TCP Connection
import StubbornTCP from "@cldmv/stubborn-tcp";
// CommonJS: const StubbornTCP = require("@cldmv/stubborn-tcp");
// Modern options-based API
const client = new StubbornTCP({
host: "localhost",
port: 8080,
debug: true
});
// Event-driven approach
client.on("connect", () => console.log("Connected!"));
client.on("data", (data) => console.log("Received:", data.toString()));
client.on("disconnect", () => console.log("Disconnected, will retry..."));
client.on("debug", (message) => console.log(message));
// Send data
client.write("Hello, server!");TLS/SSL Secure Connection
const secureClient = new StubbornTCP({
host: "secure.example.com",
port: 443,
tls: true,
tlsOptions: {
rejectUnauthorized: true,
servername: "secure.example.com"
}
});
secureClient.on("secureConnect", () => {
console.log("Secure connection established");
console.log("Certificate:", secureClient.getPeerCertificate());
});Advanced Configuration
const client = new StubbornTCP({
host: "localhost",
port: 8080,
// Connection options
keepAlive: true,
keepAliveInitialDelay: 30000,
noDelay: true,
connectionTimeout: 5000,
// Reconnection strategy
autoReconnect: true,
reconnectDelay: 1000,
maxReconnectDelay: 30000,
reconnectBackoffFactor: 2.0,
maxReconnectAttempts: 10,
// Application heartbeat
heartbeatEnabled: true,
heartbeatInterval: 15000,
heartbeatFunc: [(client) => client.write("PING"), (client) => client.write("KEEPALIVE")]
});📖 API Reference
Constructor
new StubbornTCP(options);Configuration Options:
Connection Options
host(string): Host to connect to (also the default foropen()called without arguments)port(number): Port to connect to (also the default foropen()called without arguments). With bothhostandportset, the client connects immediatelydebug(boolean, default: false): Emitdebugevents with diagnostic messages
Socket Options
keepAlive(boolean, default: true): Enable TCP keep-alivekeepAliveInitialDelay(number, default: 60000): Keep-alive initial delay (ms)noDelay(boolean, default: true): Disable Nagle's algorithmconnectionTimeout(number, default: 10000): Connect-phase timeout (ms). Bounds how long each connection attempt (including the TLS handshake) may take; a timed-out attempt is closed and goes through the normal reconnect path. It is cleared once connected, so it never closes an established connection.0or negative disables itidleTimeout(number, default: 0): Idle timeout (ms) for an established connection — with no socket activity for this long, the connection is timed out and closed (and reconnected ifautoReconnectis on).0or negative disables it (the default)
TLS/SSL Options
tls(boolean, default: false): Enable TLS encryptiontlsOptions(object, default: {}): TLS configuration (certificates, etc.)
Reconnection Options
autoReconnect(boolean, default: true): Enable auto-reconnectionreconnectDelay(number, default: 1000): Initial reconnect delay (ms)maxReconnectDelay(number, default: 30000): Maximum reconnect delay (ms)reconnectBackoffFactor(number, default: 1.5): Exponential backoff multipliermaxReconnectAttempts(number, default: -1): Max attempts (-1 = infinite)
Heartbeat Options
heartbeatEnabled(boolean, default: false): Enable application heartbeatheartbeatInterval(number, default: 30000): Heartbeat interval (ms)heartbeatFunc(function|array): Function(s) to call for heartbeat; each receives the client instanceheartbeatResetOnActivity(boolean, default: true): Reset timer on activity
Legacy Callback Options
rxFunc(function): Data received callbackonConnect(function): Connection established callbackonDisconnect(function): Connection lost callbackonError(function): Error occurred callbackonTimeout(function): Connection timeout callback
Methods
Connection Methods
open(host, port)- Open connection to host:port (closes an existing connection first)close()- Close connection and disable auto-reconnectwrite(data)- Send data to remote host
Socket Configuration Methods
setKeepAlive(enable, initialDelay)- Configure TCP keep-alivesetNoDelay(noDelay)- Configure Nagle's algorithmsetTimeout(timeout)- Set the connect-phase timeout (connectionTimeout); re-arms a pending attempt, never affects an established connectionsetIdleTimeout(timeout)- Set the idle timeout (idleTimeout); applies to the live connection and later ones
Heartbeat Methods
enableHeartbeat(options)- Enable heartbeat with options objectdisableHeartbeat()- Disable heartbeat functionalityaddHeartbeatFunction(functions)- Add heartbeat function(s)clearHeartbeatFunctions()- Clear all heartbeat functions
TLS Methods (TLS connections only)
getPeerCertificate()- Get peer certificate informationgetCipher()- Get cipher informationisAuthorized()- Check if connection is authorizedgetAuthorizationError()- Get authorization error details
Utility Methods
resetReconnectState()- Reset reconnection attempt counter
Properties
Connection State
connectState(read-only): 0 = disconnected, 1 = connectedopenState(read-only): Connection open statehandle(read-only): Unique connection identifier
Connection Information
isSecure(read-only): Whether connection uses TLSlocalAddress(read-only): Local IP addresslocalPort(read-only): Local port numberremoteAddress(read-only): Remote IP addressremotePort(read-only): Remote port number
Events
Most events pass the connection handle and the client instance as their last two arguments.
Connection Events
connect(handle, instance)- Connection establisheddisconnect(handle, instance)- Connection lostdata(data, handle, instance)- Data receivederror(error, handle, instance)- Error occurred (only emitted while anerrorlistener is attached; without one, errors go to thedebugevent and the client keeps reconnecting instead of crashing the process)failToConnect(error, handle, instance)- A connection attempt (the first one or a reconnect) failed beforeconnect; fires once per failed attempt, beforeerror. A timeout before connecting reports anETIMEDOUTerrortimeout(handle, instance)- A connection attempt timed out (connectionTimeout), or an established connection was idle foridleTimeout
Reconnection Events
maxReconnectAttemptsReached(attempts)- Max reconnect attempts reached
Heartbeat Events
heartbeat(handle, instance)- Heartbeat sentheartbeatFailed(error, functionIndex, handle, instance)- Heartbeat function failed
TLS Events (TLS connections only)
secureConnect(handle, instance)- TLS secure connection establishedkeylog(line, handle, instance)- TLS key material generated/receivedOCSPResponse(response, handle, instance)- OCSP response received
Debug Events
debug(message, handle, instance)- Diagnostic message, emitted only whendebug: true
🔄 Auto-Reconnection with Exponential Backoff
StubbornTCP features intelligent reconnection with exponential backoff:
- Initial Delay: 1000ms (configurable)
- Maximum Delay: 30000ms (configurable)
- Backoff Factor: 1.5x (configurable)
- Unlimited Attempts: By default (configurable)
- Reset on Success: Delay resets after successful connection
🧪 Advanced Examples
Complete Configuration Example
import fs from "node:fs";
import StubbornTCP from "@cldmv/stubborn-tcp";
// host and port are given, so the client connects immediately
const client = new StubbornTCP({
// Connection settings
host: "localhost",
port: 8080,
debug: true,
// Socket optimization
keepAlive: true,
keepAliveInitialDelay: 60000,
noDelay: true,
connectionTimeout: 15000,
// TLS encryption
tls: true,
tlsOptions: {
rejectUnauthorized: false,
cert: fs.readFileSync("client-cert.pem"),
key: fs.readFileSync("client-key.pem"),
ca: fs.readFileSync("ca-cert.pem")
},
// Reconnection strategy
autoReconnect: true,
reconnectDelay: 2000,
maxReconnectDelay: 60000,
reconnectBackoffFactor: 2.0,
maxReconnectAttempts: 10,
// Application heartbeat
heartbeatEnabled: true,
heartbeatInterval: 30000,
heartbeatFunc: [(client) => client.write("PING\n"), (client) => console.log("💓 Heartbeat sent")]
});
// Event-driven approach
client.on("connect", (handle, instance) => {
console.log("🔗 Connected securely");
instance.write("Hello secure server!");
});
client.on("data", (data, handle, instance) => {
console.log("📨 Received:", data.toString());
});
client.on("secureConnect", () => {
const cert = client.getPeerCertificate();
console.log("🔒 TLS Certificate:", cert.subject);
});Heartbeat with Multiple Functions
const client = new StubbornTCP();
// Enable heartbeat with multiple functions
client.enableHeartbeat({
interval: 15000,
resetOnActivity: true,
functions: [(client) => client.write("KEEPALIVE\n"), (client) => console.log("💓 Keep-alive sent"), (client) => updateLastHeartbeat()]
});
client.on("heartbeatFailed", (error) => {
console.error("💔 Heartbeat failed:", error.message);
});
client.open("localhost", 8080);Socket Configuration at Runtime
const client = new StubbornTCP();
client.on("connect", () => {
// Optimize socket after connection
client.setKeepAlive(true, 30000);
client.setNoDelay(true);
client.setIdleTimeout(60000);
console.log("Socket optimized for performance");
});
client.open("api.example.com", 443);🔁 Migration from Legacy API
The constructor only accepts an options object. Code written against the older positional constructor needs its arguments moved into options; the legacy Open() and Write() methods and the OnConnectFunc / OnDisconnectFunc properties are still available as aliases.
Old Style (Deprecated)
const client = new StubbornTCP(rxFunc, "localhost", 8080, instance, bufferSize, debug);
client.OnConnectFunc = onConnect;
client.OnDisconnectFunc = onDisconnect;
client.Open("localhost", 8080);New Style (Recommended)
// Connects immediately because host and port are given
const client = new StubbornTCP({
host: "localhost",
port: 8080,
debug,
rxFunc,
onConnect,
onDisconnect
});🛡 Error Handling
The client handles various error conditions gracefully:
- Connection refused: Automatically retries connection
- Network timeouts: Continues reconnection attempts
- Server disconnection: Attempts to reconnect after delay
- Write errors: Returns false, connection state updated
📚 Documentation
- Usage examples — ESM and CommonJS usage
- Changelog — release notes for every version
🤝 Contributing
- Fork the repository
- Create your feature branch off
next(git checkout -b feat/amazing-feature origin/next) - Commit your changes (
git commit -m 'feat: add some amazing feature') - Push to the branch (
git push origin feat/amazing-feature) - Open a Pull Request into
next
🔗 Links
- npm: @cldmv/stubborn-tcp
- GitHub: CLDMV/stubborn-tcp
- Issues: GitHub Issues
- Changelog: docs/changelog/
📄 License
This project is licensed under the Apache License Version 2.0 - see the LICENSE file for details.
