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

@powforge/captcha

v0.1.1

Published

Self-hosted proof-of-work CAPTCHA. Privacy-first reCAPTCHA alternative with zero tracking.

Downloads

267

Readme

@powforge/captcha

Self-hosted reCAPTCHA alternative using proof-of-work. No tracking, no cookies, no external dependencies.

Your users prove they're human by computing SHA-256 hashes in their browser. No data leaves your site. No third-party scripts. Works offline once loaded.

Quick Start

1. Install

npm install @powforge/captcha

Or use a script tag (no build step needed):

<script src="https://unpkg.com/@powforge/captcha/dist/powforge-captcha.min.js"></script>

2. Add the widget

<form action="/submit" method="POST">
  <input type="text" name="email" placeholder="Email">

  <!-- CAPTCHA mounts here -->
  <div id="pow-captcha"></div>
  <input type="hidden" name="pf_token">

  <button type="submit">Submit</button>
</form>

<script src="powforge-captcha.min.js"
        data-target="#pow-captcha"
        data-server="https://captcha.powforge.dev"
        data-theme="dark"></script>

3. Verify server-side

const { verifyToken } = require('@powforge/captcha/verify');

app.post('/submit', async (req, res) => {
  const result = await verifyToken(req.body.pf_token, {
    server: 'https://captcha.powforge.dev',
  });

  if (!result.valid) {
    return res.status(403).json({ error: 'CAPTCHA failed' });
  }

  // User verified - process form...
});

How It Works

  1. Your page loads the widget (5KB gzipped)
  2. Widget fetches a challenge from the verification server
  3. A Web Worker mines SHA-256 hashes until it finds one with enough leading zero bits
  4. The solution is sent to the server for verification
  5. Server returns a signed token your backend can validate

Default difficulty: 16 bits (~65,536 hashes, ~4 seconds on average hardware). Adjustable per-challenge.

The proof-of-work is real energy expenditure. Bots pay a real cost. Humans wait a few seconds. No behavioral tracking, no fingerprinting, no cookies.

API

Script Tag (zero config)

<script src="powforge-captcha.min.js"
        data-target="#my-div"
        data-server="https://captcha.powforge.dev"
        data-theme="dark"
        data-difficulty="16"
        data-callback="onVerified">
</script>

| Attribute | Default | Description | |-----------|---------|-------------| | data-target | (required) | CSS selector for mount element | | data-server | https://captcha.powforge.dev | Verification server URL | | data-theme | dark | dark or light | | data-difficulty | 16 | Leading zero bits required | | data-callback | none | Global function called with (token, method) |

ES Module / npm

import { PowCaptcha } from '@powforge/captcha';

const captcha = new PowCaptcha({
  target: '#pow-captcha',
  server: 'https://captcha.powforge.dev',
  theme: 'dark',
  difficulty: 16,
});

captcha.on('verified', ({ token, method }) => {
  console.log('Token:', token);
});

captcha.on('progress', ({ nonce, elapsed, percent }) => {
  console.log(`${percent.toFixed(0)}% complete`);
});

captcha.on('error', (err) => {
  console.error('CAPTCHA error:', err);
});

Methods

| Method | Description | |--------|-------------| | start() | Fetch challenge and begin solving | | reset() | Destroy current work, fetch new challenge | | getToken() | Returns token string or null | | destroy() | Terminate worker, free resources | | on(event, fn) | Listen for events |

Events

| Event | Payload | Description | |-------|---------|-------------| | verified | { token, method } | Challenge solved and verified | | progress | { nonce, elapsed, percent } | Mining progress update | | error | Error | Challenge fetch or solve failure |

DOM Events

The widget also fires a powforge:token CustomEvent on window:

window.addEventListener('powforge:token', (e) => {
  console.log(e.detail.token, e.detail.method);
});

Hidden Input

If your form contains <input type="hidden" name="pf_token">, it is auto-filled when the CAPTCHA is solved.

Server Verification

const { verifyToken } = require('@powforge/captcha/verify');
// or: import { verifyToken } from '@powforge/captcha/verify';

const result = await verifyToken(token, {
  server: 'https://captcha.powforge.dev',  // your self-hosted URL
  timeout: 5000,                            // ms
});

// result: { valid: true, method: 'pow', issued_at: '...', expires_at: '...' }
// or:     { valid: false, reason: 'expired token' }

Comparison

| Feature | PowForge | reCAPTCHA | hCaptcha | ALTCHA | Turnstile | |---------|----------|-----------|----------|--------|-----------| | Privacy | No tracking | Google tracking | Fingerprinting | No tracking | Cloudflare tracking | | Self-hosted | Yes | No | No | Yes | No | | Dependencies | 0 | Google JS | hCaptcha JS | 0 | Cloudflare JS | | Method | SHA-256 PoW | ML behavior | ML + labeling | PoW | ML behavior | | Bundle size | ~8KB gz | ~150KB | ~120KB | ~30KB | ~80KB | | Open source | MIT | No | No | MIT | No | | Cookies | None | Yes | Yes | None | Yes | | Works offline | Once loaded | No | No | Once loaded | No | | Lightning skip | Yes | No | No | No | No | | Cost | Free / self-host | Free tier + paid | Free tier + paid | Free / self-host | Free tier |

Self-Hosted Server

Run your own verification server:

# Clone the server
git clone https://gitlab.com/powforge/captcha.git
cd captcha

# The server is a single Node.js file, zero npm dependencies for core
node server.js
# Listening on port 3077

Then point the widget at your server:

<script src="powforge-captcha.min.js"
        data-target="#captcha"
        data-server="https://your-server.com:3077">
</script>

Server endpoints:

| Endpoint | Method | Description | |----------|--------|-------------| | /api/challenge | GET | Get a new PoW challenge | | /api/verify | POST | Submit solution, get token | | /api/token/verify | POST | Server-side token validation |

Difficulty Guide

| Bits | Expected Hashes | Avg Time (desktop) | Use Case | |------|----------------|---------------------|----------| | 10 | ~1,024 | <1s | Low friction, comment forms | | 14 | ~16,384 | ~1s | Standard forms | | 16 | ~65,536 | ~4s | Default. Good balance | | 18 | ~262,144 | ~15s | High-value actions | | 20 | ~1,048,576 | ~60s | Rate limiting, account creation |

License

MIT