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/workers-crypto

v0.1.2

Published

Bytes and text for tokens, signatures and webhooks on WebCrypto: base64url and base64 both ways, hex, digests (MD5 too in Workers, for payment gateways), HMAC-SHA256 signing and constant-time checking, labelled keyed hashes, a constant-time comparison and

Readme

@codefusion-cc/workers-crypto

Bytes and text the way Workers code needs them for tokens, signatures and webhooks. Built on WebCrypto only, so it runs in Workers, Node and the browser. The one exception is MD5, which only Workers have.

npm install @codefusion-cc/workers-crypto
import { base64UrlToBytes, digestHex, hmacMatches, hmacSha256, keyedHash, randomToken, safeEqual } from '@codefusion-cc/workers-crypto'

const token = randomToken() // 32 random bytes, 43 characters of base64url
const bytes = base64UrlToBytes(header) // null when it is not base64url: no exception to catch
const ok = safeEqual(await digestHex('SHA-384', JSON.stringify(fields)), notification.sign)

// Sign a webhook body, and check the signature a provider sent, in constant time.
headers.set('x-signature', await hmacSha256(env.WEBHOOK_SECRET, body, 'hex'))
const genuine = await hmacMatches(env.WEBHOOK_SECRET, body, request.headers.get('x-signature') ?? '', 'hex')

// An id that cannot be traced back to the visitor, and matches nothing else the secret signs.
const visitor = await keyedHash(env.VISITOR_HASH_SECRET, 'console-visitor', visitorId, { bytes: 8 })
  • Every decoder returns null for text it does not read: base64UrlToBytes, base64ToBytes, hexToBytes and base64UrlToUtf8. Each value has one spelling. Whitespace is refused, and so are unused bits set in the last character, which a lenient decoder would read as the same bytes.
  • base64UrlToBytes refuses = padding unless { padded: true } is passed, because tokens and JWTs never have it.
  • base64ToBytes reads standard base64 and requires its padding unless { padding: 'optional' } is passed.
  • hmacSha256(secret, data, encoding?) takes the secret as text (UTF-8, as Worker secrets are set), bytes, or a CryptoKey. It returns 32 bytes, or text in 'hex', 'base64' or 'base64url'. A text secret is imported once per isolate. An empty or missing secret, or a CryptoKey for another algorithm or hash, rejects with a TypeError.
  • hmacMatches(secret, data, mac, encoding?) compares in constant time. Never compare two MACs with ===. A text MAC needs its encoding (a TypeError without one); a MAC in the wrong encoding or of the wrong length does not match.
  • keyedHash(secret, label, value, { bytes, encoding }) is the HMAC of label\nvalue, cut to bytes. The label keeps one use's hashes apart from another's under the same secret. It must be one line.
  • Never use one secret for both keyedHash and hmacSha256. keyedHash is the hmacSha256 of the label, a line break and the value, so a MAC over data shaped that way equals a keyed hash. Give each its own secret.
  • safeEqual takes time that does not depend on where two texts differ. Only a length difference shows.
  • digestHex('MD5' | 'SHA-1', …) is only for providers whose signatures require those algorithms. Some payment gateways do.

Code built on WebCrypto

Two things every package that imports keys or opens sealed data needs, kept here once.

import { createPromiseCache, nullIfRefused } from '@codefusion-cc/workers-crypto'

// Import each key once per isolate (or page), at most 64 of them.
const imported = createPromiseCache<CryptoKey>(64)
const keyFor = (raw: string) => imported(raw, () => crypto.subtle.importKey('raw', decode(raw), 'AES-GCM', false, ['decrypt']))

// null when the tag does not match (tampered, or another key); an outage rejects as itself.
const plain = await nullIfRefused(crypto.subtle.decrypt({ name: 'AES-GCM', iv }, await keyFor(raw), data), 'OperationError')
  • createPromiseCache<T>(limit) returns (key, make) => Promise<T>. Calls for a key share one run of make(key), and its result serves every later call. It keeps at most limit results, work still running among them, and lets the least recently used go first. A rejection is never kept: the callers waiting on it see it, and the next call runs make again, so one failed import does not break that key for the rest of the isolate's life. A null result is an answer and is kept.
  • nullIfRefused(call, ...names) resolves what a WebCrypto call resolves, or null when WebCrypto refuses the input with one of names: OperationError (an AES-GCM tag that does not match, an ECDH or X25519 point that gives no secret), DataError (key data that is no key), InvalidAccessError (a key of another kind than the operation). Every other failure (an algorithm the runtime lacks, a broken binding, an outage) rejects as itself. Catching everything instead turns an outage into "no key", "tampered" or "forged", and code that acts on those (writes new keys, deletes the data, signs the person out) then does damage. refusedWith(error, ...names) is the same test for a catch block. Both read the error's name alone, so a DOMException from another realm still counts.