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

baileys-keep-alive

v0.1.0

Published

Automatic reconnect, heartbeat, and QR-stale detection for @whiskeysockets/baileys connections

Downloads

145

Readme

baileys-keep-alive

npm license MIT Sister: baileys-antiban

Automatic reconnect, heartbeat, and QR-stale detection for @whiskeysockets/baileys connections. Sister library to baileys-antiban.

What it does

Drop-in connection lifecycle manager for Baileys:

  • Auto-reconnect on close with exponential backoff + jitter
  • Reason-aware — gives up on loggedOut, retries everything else
  • Heartbeat — keeps session warm with periodic presence updates
  • QR-stale detection — alerts when QR sits unscanned beyond threshold
  • Alert hooks — wire into Sentry, Telegram, Discord (no hard deps)

What it doesn't do

  • Not anti-ban (that's baileys-antiban's job)
  • Not session storage (use WaSP or useMultiFileAuthState)
  • Not multi-tenant orchestration (deliberately scoped to ONE socket)
  • Not opinionated about logger (accepts any { info, warn, error })

Install

npm install baileys-keep-alive @whiskeysockets/baileys

Quick Start

import { makeWASocket, useMultiFileAuthState } from '@whiskeysockets/baileys';
import { keepAlive } from 'baileys-keep-alive';

const { state, saveCreds } = await useMultiFileAuthState('auth_info');

const sock = makeWASocket({
  auth: state,
  printQRInTerminal: true,
});

sock.ev.on('creds.update', saveCreds);

const ka = keepAlive(sock, {
  reconnectFactory: async () => {
    return makeWASocket({
      auth: state,
      printQRInTerminal: true,
    });
  },
});

// ka.stop() to dispose

Options

keepAlive(sock, {
  // Reconnect config
  reconnect?: {
    enabled?: boolean;          // default: true
    initialDelayMs?: number;    // default: 1000
    maxDelayMs?: number;        // default: 60_000
    factor?: number;            // default: 2
    jitterMs?: number;          // default: 500
    maxRetries?: number;        // default: Infinity
  };

  // Heartbeat config
  heartbeat?: {
    enabled?: boolean;          // default: true
    intervalMs?: number;        // default: 30_000
    presence?: 'available' | 'unavailable'; // default: 'available'
  };

  // QR-stale config
  qr?: {
    staleAfterMs?: number;      // default: 60_000
  };

  // Alert hooks (all optional, sync or async)
  onReconnect?: (attempt: number, delayMs: number) => void | Promise<void>;
  onReconnectFailed?: (err: Error, attempt: number) => void | Promise<void>;
  onMaxRetries?: (totalAttempts: number) => void | Promise<void>;
  onQRStale?: (ageMs: number) => void | Promise<void>;
  onLoggedOut?: () => void | Promise<void>;
  onHeartbeat?: () => void | Promise<void>;

  // Required for reconnect
  reconnectFactory?: () => Promise<WASocket> | WASocket;

  // Logger (default: NoopLogger)
  logger?: { info, warn, error };
});

Handle API

const ka = keepAlive(sock, { ... });

ka.stop();            // dispose timers + listeners
ka.isActive();        // boolean
ka.getStats();        // { reconnects, lastReconnectAt, heartbeatsSent }

Backoff Formula

delay = min(initial * factor^(attempt-1) + random(0..jitter), maxDelay)

Example with defaults (initial=1s, factor=2, jitter=500ms, max=60s):

  • Attempt 1: 1s + [0..500ms]
  • Attempt 2: 2s + [0..500ms]
  • Attempt 3: 4s + [0..500ms]
  • Attempt 4: 8s + [0..500ms]
  • Attempt 5: 16s + [0..500ms]
  • ...caps at 60s

Important: Socket Replacement

When reconnect succeeds, keepAlive switches to the new socket internally. If you need the new socket reference in your code, wrap it or re-import from your factory:

let currentSock = makeWASocket({ ... });

const ka = keepAlive(currentSock, {
  reconnectFactory: async () => {
    const newSock = makeWASocket({ ... });
    currentSock = newSock; // update your reference
    return newSock;
  },
});

// Use currentSock in your app

Examples

See examples/:

  • basic.ts — minimal usage
  • with-alerts.ts — Sentry + Telegram hooks (commented)
  • multi-handler.ts — all hooks + custom config

Known Limitations (v0.1)

This release treats the disconnect-reason space coarsely: only loggedOut (HTTP 401) is terminal; everything else triggers reconnect with backoff. That covers ~90% of real-world cases but misses some nuanced ones.

WhatsApp Web's session protocol — see the foundational sigalor/whatsapp-web-reveng research that seeded Baileys — uses an HKDF key chain. When the chain desyncs (e.g. badSession, restoreCorrupted) or a different device claims the socket (connectionReplaced / 440), simple reconnect won't help — you need to reset session state or surrender the slot. v0.1 doesn't distinguish these cases.

If you hit these, listen on connection.update yourself for the specific lastDisconnect.error.output.statusCode and short-circuit keepAlive via handle.stop() before it spins fruitless retries.

Planned for v0.2:

  • onSessionCorrupted callback (badSession / restoreCorrupted)
  • onConnectionReplaced callback (440)
  • onMultideviceMismatch callback
  • Optional auto-purge-state hook so session-corruption cases can self-heal

Philosophy

Built for production WhatsApp bots that need to stay online. No magic, no bloat. You provide the socket factory, we handle the retry loop. Pairs with:

License

MIT © Hannes (Kobus) Wentzel