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

@007captcha/server

v0.1.0

Published

007captcha server-side token verification

Readme


Handles everything security-sensitive: challenge generation, behavioral analysis, scoring, and HMAC-SHA256 token signing. The client widget acts as a thin rendering layer — all verification logic runs here.

Zero runtime dependencies. Uses only Node.js built-in crypto.

Installation

pnpm add @007captcha/server

Token Verification

Verify signed tokens from any challenge method:

import { verify } from '@007captcha/server';

const result = await verify(token, SECRET);

if (result.success) {
  // Allow the request
}

verify(token, secretKey): Promise<VerifyResult>

| Field | Type | Description | |-------|------|-------------| | success | boolean | true if valid signature and not flagged as bot | | score | number | 0.0 (bot) to 1.0 (human) | | method | string | 'shape', 'maze', or 'ball' | | challenge | string | Specific challenge identifier | | verdict | string | 'human', 'uncertain', or 'bot' | | timestamp | number | When the challenge was completed | | error | string? | Reason if verification failed |

Tokens are single-use and expire after 5 minutes.

Challenge Managers

Each challenge method has a session manager that handles the full lifecycle: session creation, challenge delivery, input analysis, and token signing. Create one instance per server process.

Ball — BallChallengeManager

The ball challenge generates a physics-based trajectory in real-time and streams rendered frames to the client via SSE. After the challenge, the user's cursor path is analyzed against the recorded trajectory.

import { BallChallengeManager } from '@007captcha/server';
const ball = new BallChallengeManager(SECRET);

Endpoints required:

| Method | Path | Handler | |--------|------|---------| | POST | /captcha/ball/start | ball.createSession() | | GET | /captcha/ball/:id/stream | ball.startStreaming(id, onFrame, onEnd) | | POST | /captcha/ball/:id/verify | ball.verify(id, points, cursorStartT, origin) |

Maze — MazeChallengeManager

Generates a procedural maze, renders it as a PNG, and solves it server-side. The client receives only the image and zone coordinates. Cursor path analysis runs entirely on the server.

import { MazeChallengeManager } from '@007captcha/server';
const maze = new MazeChallengeManager(SECRET);

Endpoints required:

| Method | Path | Handler | |--------|------|---------| | POST | /captcha/maze/start | maze.createSession() | | POST | /captcha/maze/:id/verify | maze.verify(id, points, origin) |

Shape — ShapeChallengeManager

Assigns a random shape (circle, triangle, or square) and analyzes the user's drawing server-side. The client only knows which shape to draw — scoring and detection run here.

import { ShapeChallengeManager } from '@007captcha/server';
const shape = new ShapeChallengeManager(SECRET);

Endpoints required:

| Method | Path | Handler | |--------|------|---------| | POST | /captcha/shape/start | shape.createSession() | | POST | /captcha/shape/:id/verify | shape.verify(id, points, origin) |

Express Integration

Full working example with all three methods:

import express from 'express';
import {
  verify,
  BallChallengeManager,
  MazeChallengeManager,
  ShapeChallengeManager,
} from '@007captcha/server';

const app = express();
const SECRET = process.env.CAPTCHA_SECRET;

const ball  = new BallChallengeManager(SECRET);
const maze  = new MazeChallengeManager(SECRET);
const shape = new ShapeChallengeManager(SECRET);

app.use(express.json());

// Ball
app.post('/captcha/ball/start', (req, res) => {
  res.json(ball.createSession());
});

app.get('/captcha/ball/:id/stream', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  });
  let done = false;
  const ok = ball.startStreaming(
    req.params.id,
    (frame) => res.write(`event: frame\ndata: ${JSON.stringify(frame)}\n\n`),
    ()      => { done = true; res.write('event: end\ndata: {}\n\n'); res.end(); },
  );
  if (!ok) { res.end(); return; }
  req.on('close', () => { if (!done) ball.cancelSession(req.params.id); });
});

app.post('/captcha/ball/:id/verify', (req, res) => {
  const { points, cursorStartT, origin } = req.body;
  res.json(ball.verify(req.params.id, points || [], cursorStartT || 0, origin || ''));
});

// Maze
app.post('/captcha/maze/start', (req, res) => res.json(maze.createSession()));
app.post('/captcha/maze/:id/verify', (req, res) => {
  res.json(maze.verify(req.params.id, req.body.points || [], req.body.origin || ''));
});

// Shape
app.post('/captcha/shape/start', (req, res) => res.json(shape.createSession()));
app.post('/captcha/shape/:id/verify', (req, res) => {
  res.json(shape.verify(req.params.id, req.body.points || [], req.body.origin || ''));
});

// Token verification
app.post('/verify', async (req, res) => {
  res.json(await verify(req.body.token || '', SECRET));
});

app.listen(3007);

See examples/express-server/ for the full demo with a UI.

Session Lifecycle

  • Sessions are stored in memory and auto-expire (60s for ball/shape, 120s for maze).
  • Each session can only be verified once.
  • Call manager.destroy() on server shutdown to clean up timers.
  • For horizontally scaled deployments, sessions are per-process — route challenge requests to the same instance (sticky sessions or a shared store).

License

MIT