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

@codefusion-cc/turnstile

v0.1.0

Published

Cloudflare Turnstile for a form: the server check (Siteverify as a result, with a timeout, one retry under an idempotency key, hostname and action checks, no fail-open), the page's widget (script loader, one fresh token per submit, blocked and expired sta

Readme

@codefusion-cc/turnstile

Cloudflare Turnstile for a form: the check on the server, the widget in the page, and a fake Cloudflare for tests.

  • . (Workers, WebCrypto and fetch only): verifyTurnstile, the Siteverify call as a result, and isTestingKey.
  • ./browser: the script loader and createTurnstile, a controller that renders the widget and hands out one fresh token per submit.
  • ./react: useTurnstile, the same as a hook.
  • ./testing: fakeSiteverify, Cloudflare's published test keys.

No dependencies; React is an optional peer dependency of ./react.

npm install @codefusion-cc/turnstile

On the server

import { verifyTurnstile } from '@codefusion-cc/turnstile'

const check = await verifyTurnstile({
  secret: env.TURNSTILE_SECRET_KEY,
  token: body.turnstile,                       // whatever the page sent, even if it is not a string
  remoteIp: request.headers.get('cf-connecting-ip'),
  hostnames: ['shop.example', 'www.shop.example'],
  action: 'checkout',
})
if (!check.ok) return refuse(check.reason)    // before anything is written or counted

It never throws for what happens at run time and never lets a form through that it could not check:

| reason | Meaning | Tell the visitor | | --- | --- | --- | | missing, too_long | no token, or one over 2048 characters (no call is made) | the check did not finish; try again | | rejected | Cloudflare does not know the token | the check failed; try again | | used_or_expired | spent before, or older than five minutes (Cloudflare cannot tell which) | the check ran out; try again | | hostname, action | a real token from another site, or for another form | the check failed | | testing_key | the answer, a yes or a no, came from a published test key (refused unless allowTestingKey) | nothing; log it | | misconfigured | no secret, no hostnames, no action, or Cloudflare refuses the secret | nothing; log it, it is yours to fix | | unavailable | no usable answer from Cloudflare after one retry | the shop cannot check right now; try again |

  • Fail closed. unavailable and misconfigured are refusals. Whether to let an order through while Cloudflare is down is the caller's decision, and the default is no; a shop with other limits behind the check should still weigh that those limits are the weaker net.
  • Timeout and retry. One attempt waits timeoutMs (3000 ms), a failed attempt is asked again once (retries). Network errors, no answer, HTTP 5xx and 429, a redirect (never followed), a body that is not Siteverify's and internal-error count as no answer.
  • Idempotency. Every attempt of one call carries the same idempotency_key, so a token Cloudflare consumed on an attempt whose answer was lost is not refused as a duplicate on the retry.
  • Hostname and action must match. Without them a token made on any site with any widget would pass.
  • Single use. Cloudflare accepts a token once; asking again is used_or_expired.
  • Test keys. Cloudflare's test secrets answer any token, with hostname example.com, no action, and metadata.result_with_testing_key. By default such an answer is refused (testing_key), so a test secret left in production opens nothing; pass allowTestingKey: true in development and tests only. isTestingKey(key) tells a published test key (site key or secret) from a real one without a call, for a health check that reports the form as not set up before the first visitor meets it.
  • The secret is sent without the blanks around it (a pasted secret often ends with a line end, which Cloudflare refuses).

The result and the log line (detail) hold neither the token nor the secret.

In the page

import { useTurnstile } from '@codefusion-cc/turnstile/react'

const check = useTurnstile(siteKey ? { siteKey, action: 'checkout', language: 'pl', theme: 'dark' } : null)

<div ref={check.ref} />                     // the widget draws here; give it the room you want to keep

async function submit() {
  const got = await check.take()            // waits for a running check, then hands out the token once
  if (!got.ok) return explain(got.reason)   // 'blocked' | 'failed' | 'timeout' | 'destroyed'
  await send({ ...form, turnstile: got.token })
}
  • check.snapshot.phase: loading, working, interactive (Cloudflare shows a box the visitor must use), ready, failed (Cloudflare reports errorCode and retries), blocked (the script did not load: an ad blocker, a firewall, no network; the page retries when the browser is back online, check.retry() does it on demand, and so does the next take(), which waits for that load), off.
  • Default appearance: 'interaction-only': most visitors see nothing. The element has no height of its own to keep; the page decides what to show in each phase.
  • One token per submit. take() hands out the token and starts the next check at once, so a retry after a refused submit already has a fresh one.
  • An expired token is replaced by Cloudflare by itself (refresh-expired).
  • size: 'auto': flexible when the element is at least 300 px wide, otherwise compact (150 px), so a 320 px phone does not scroll sideways. The width is read when the widget is drawn, not when the window changes later.
  • A new theme or language keeps a token already held and shows in the next check; a check in progress starts over with them.
  • interactive can come before the visitor submits. Show the box then, but move the page to it only while a take() is waiting.

The page's Content-Security-Policy needs https://challenges.cloudflare.com in script-src and frame-src, and nothing in connect-src: the widget talks to Cloudflare from its own frame.

Tests

import { fakeSiteverify, TEST_SECRET_KEYS, TEST_SITE_KEYS } from '@codefusion-cc/turnstile/testing'

const cloudflare = fakeSiteverify({ claims: { hostname: 'shop.example', action: 'checkout' } })
const token = cloudflare.issue()
cloudflare.fail('hang', 2)                  // also network, http500, http429, not-json, internal-error, lose

The fake knows the tokens it issued (single use, the idempotency key, issueExpired), answers only Cloudflare's address, and records every request. It answers TEST_SECRET_KEYS as the live Siteverify does (any token, none spent, the test-key mark), so a test can prove that a real site refuses them. TEST_SITE_KEYS and TEST_SECRET_KEYS are Cloudflare's published keys for local runs: pass, block, and a widget that forces the visible box (interactive) or a secret that answers timeout-or-duplicate (spent); they are the only keys a repository may hold.