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

@maggidev/captchashield

v1.0.1

Published

Customizable Cloudflare Turnstile modal with secure defaults and optional client-cookie skip logic.

Readme

npm version npm downloads bundle size CI types license

Why

CaptchaShield exists for teams that want a straightforward Turnstile integration without rebuilding the same modal, cleanup, retry, and verification flow for every project.

  • It keeps the browser-side integration small and focused.
  • It supports both the built-in modal and custom renderers.
  • It treats client persistence as explicit opt-in instead of a hidden default.
  • It makes local testing easier with a dedicated demo page and mock widget.
  • It is designed to stay honest about what is UX and what must still be enforced on the backend.

Technologies

  • TypeScript for the package surface and internal logic
  • Cloudflare Turnstile as the challenge provider
  • tsup for package bundling
  • vitest with jsdom for unit and behavior tests
  • eslint for static linting
  • a small Node HTTP server for the local demo lab

Demo

The visuals below are from the local demo page in demo/. They show one testing surface only. They do not represent every possible integration style, every renderer, or a recommended production design for all consumers of the package.

Run the local demo:

npm run demo

Then open:

http://127.0.0.1:4173/demo/

No Cloudflare account needed for local testing. The demo ships with a built-in mock Turnstile widget that fires the token callback immediately. You can also use Cloudflare's public test site keys (1x00000000000000000000AA always passes, 2x00000000000000000000AB always blocks) against the real Turnstile script.

The demo page includes:

  • default modal and custom renderer flows
  • local mock Turnstile behavior
  • local verify and status endpoints
  • session-only versus trusted-cookie behavior
  • tamper simulation
  • live config preview and event log

How It Works

Verification Flow

sequenceDiagram
    participant User
    participant App
    participant CaptchaShield
    participant Turnstile
    participant Backend

    User->>App: Request protected action
    App->>CaptchaShield: open()
    alt already verified in session or trusted cookie
        CaptchaShield-->>App: already-verified
    else needs challenge
        CaptchaShield->>Turnstile: load script and render widget
        Turnstile-->>CaptchaShield: token
        CaptchaShield->>Backend: POST verify(token)
        alt accepted
            Backend-->>CaptchaShield: 2xx
            CaptchaShield-->>App: rendered / verified
        else rejected
            Backend-->>CaptchaShield: non-2xx
            CaptchaShield->>Turnstile: reset widget
            CaptchaShield-->>App: onError(...)
        end
    end

Package Responsibilities

flowchart LR
    A["App code"] --> B["createCaptchaShield(config)"]
    B --> C["Script loading and validation"]
    B --> D["Modal or custom renderer"]
    B --> E["Verification request handling"]
    B --> F["Session and optional trusted cookie state"]
    B --> G["Lifecycle cleanup and reset"]

Demo Lab Surface

flowchart TB
    UI["demo/index.html"] --> APP["demo/app.js"]
    APP --> LIB["dist/index.mjs"]
    APP --> MOCK["Mock Turnstile widget"]
    APP --> STATUS["/api/status"]
    APP --> VERIFY["/api/verify"]
    STATUS --> SERVER["scripts/serve-demo.mjs"]
    VERIFY --> SERVER

Verified State

stateDiagram-v2
    [*] --> Unverified
    Unverified --> SessionVerified: verify success
    SessionVerified --> Unverified: reset() or destroy()
    SessionVerified --> TrustedCookieVerified: trustClientCookie enabled
    TrustedCookieVerified --> Unverified: cookie cleared or expired

What The Package Handles

  • Turnstile script loading from the official Cloudflare host
  • built-in modal rendering with sane defaults
  • custom render hook support
  • token verification requests with timeout and retry handling
  • widget reset and cleanup after reject or error
  • optional status precheck before rendering
  • challenge presence and removal monitoring
  • typed error callbacks

What Your Backend Still Must Handle

  • final authorization decisions
  • Turnstile secret management
  • token verification against Cloudflare siteverify
  • route protection and abuse policy
  • rate limiting, IP policy, and application-specific trust decisions

Install

npm install @maggidev/captchashield

Quick Start

import { createCaptchaShield } from '@maggidev/captchashield';

const shield = createCaptchaShield({
  siteKey: '<your-turnstile-sitekey>',
  verify: {
    endpoint: '/api/turnstile/verify',
  },
  onVerified: (token) => {
    console.log('Verified token', token);
  },
  onError: (error) => {
    console.error(error.message);
  },
});

await shield.open();

The verify.endpoint receives a POST request with Content-Type: application/json and body { "token": "<turnstile-token>" }. Any 2xx response is treated as success; anything else triggers onError.

By default, verified state is session-local. Persistent skip via cookie only happens when cookie.trustClientCookie is enabled.

Security Model

CaptchaShield improves Turnstile UX. It is not a substitute for backend enforcement.

  • Always verify Turnstile tokens on your server for protected actions.
  • Treat client cookies as UX only. Do not use them as authorization.
  • Only enable cookie.trustClientCookie when client-side skip is acceptable for your use case.
  • Verification only supports POST, so tokens do not end up in URLs.
  • Endpoint configuration is validated and custom script loading is restricted to the official Cloudflare host.
  • cookie.name, cookie.domain, and cookie.path are validated against RFC 6265 at construction time — passing values with semicolons or control characters throws immediately.
  • Custom CSS is injected as-is; never pass user-generated CSS into modal.styles.customCss.

Common Config

const shield = createCaptchaShield({
  siteKey: '0x4AAAAAA...',
  cookie: {
    secure: true,
    sameSite: 'Strict',
    trustClientCookie: false,
  },
  integrity: {
    verifyTurnstileGlobal: true,
    enforceChallengePresence: true,
    monitorChallengeRemoval: true,
  },
  verify: {
    endpoint: '/api/security/verify-captcha',
    timeoutMs: 5000,
    retries: 1,
  },
});

Custom Renderer

createCaptchaShield({
  siteKey: '<sitekey>',
  render: ({ challengeContainer, close }) => {
    const root = document.createElement('div');
    const panel = document.createElement('section');
    const heading = document.createElement('h2');
    const closeButton = document.createElement('button');

    heading.textContent = 'Verification required';
    closeButton.textContent = 'Close';
    closeButton.onclick = close;

    panel.append(heading, challengeContainer, closeButton);
    root.append(panel);

    return { root };
  },
});

Minimal Backend Example

import type { Request, Response } from 'express';
import fetch from 'node-fetch';

const TURNSTILE_SECRET = process.env.TURNSTILE_SECRET!;

export async function verifyTurnstile(req: Request, res: Response) {
  const token = req.body?.token;
  if (!token) return res.status(400).json({ error: 'missing token' });

  const cfRes = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ secret: TURNSTILE_SECRET, response: token }),
  });

  const payload = await cfRes.json();
  if (payload.success) return res.sendStatus(204);
  return res.status(400).json({ success: false, error: payload['error-codes'] });
}

API At A Glance

createCaptchaShield(config) returns:

  • open(): Promise<{ status: 'rendered' | 'already-verified'; reason?: 'cookie' | 'session' }>
  • close(): remove the modal without clearing state
  • reset(): clear token, trusted cookie, and reset the widget
  • destroy(): reset and close
  • isVerified(): inspect current verified state
  • getToken(): read the last token seen by the instance

Main config areas:

  • modal: copy, classes, default style injection, custom CSS
  • cookie: name, scope, lifetime, SameSite, secure flag, trustClientCookie
  • verify: backend endpoint, timeout, retries, headers, expected status
  • statusCheck: optional preflight request before render
  • integrity: global checks, challenge presence enforcement, removal monitoring
  • render: custom renderer hook

Scripts

  • npm run dev - tsup watch
  • npm run build - build ESM, CJS, and type declarations
  • npm run test - Vitest
  • npm run lint - ESLint
  • npm run typecheck - TypeScript no-emit check
  • npm run demo - build and start the local demo page
  • npm run demo:serve - start the demo server without rebuilding

Roadmap

  • Signed skip tokens backed by the server instead of plain trusted cookies
  • First-party renderer presets for inline, sheet, and compact verification UIs
  • Better analytics hooks for render, verify success, reject, timeout, and tamper events
  • Framework adapters for Next.js, Express, edge runtimes, and Laravel
  • A small end-to-end browser test suite for demo and package regression checks

License

MIT