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

@magnaboy/webcrypto

v0.1.0

Published

WebCrypto-only primitives: byte codecs, hashing and HMAC, CSPRNG helpers, AES-GCM sealing and prefixed API tokens.

Readme

@magnaboy/webcrypto

WebCrypto-only primitives: byte codecs, hashing and HMAC, CSPRNG helpers, AES-GCM sealing and prefixed API tokens.

No node: import, no Buffer, no process, no dependencies. The same code runs on Node 20+, in a browser and inside workerd.

Bytes

utf8Encode / utf8Decode, base64Encode / base64Decode, base64UrlEncode / base64UrlDecode, utf8ToBase64 / base64ToUtf8, hexEncode / hexDecode.

hexDecode returns null rather than throwing, because callers decode attacker-supplied input and branch on the result. Base64 encoding chunks its input, so a multi-megabyte array does not blow the argument limit of String.fromCharCode.

Every returned array is a Bytes (Uint8Array<ArrayBuffer>), which WebCrypto accepts without a cast. toBytes normalises anything a caller passes in, copying a view over a SharedArrayBuffer rather than rejecting it.

Hashing

sha256Bytes / sha256Hex, hmacSha256Bytes / hmacSha256Hex, and hmacSha256Verify for webhook signatures. Verification accepts hex, sha256=-prefixed hex, base64 and base64url, because providers disagree, and compares every candidate without short-circuiting.

timingSafeEqualBytes is constant time in the contents but not in the length: a length mismatch returns immediately. Compare fixed-size digests when the length itself is secret.

Randomness

randomBytes fills in 64 KiB chunks, since getRandomValues rejects a larger request in one call. randomString uses rejection sampling — bytes at or above the largest multiple of the alphabet length are discarded rather than folded — so every character stays equally likely. randomId defaults to an alphabet with no i, l or o, so an id survives being read aloud.

Sealing

encryptSecret / decryptSecret are AES-GCM over a string, returning base64 with the IV prepended. sealToken / openToken seal a JSON payload with an expiry into a base64url token — safe in a path, a query value or a header with no escaping — stamping created_at, expires_at and a random nonce onto it.

Both take a context string as additional authenticated data, so a value sealed for password reset will not open as an email verification. Pass now to make expiry deterministic in tests.

import { generateEncryptionSecret, openToken, sealToken } from '@magnaboy/webcrypto';

const secret = generateEncryptionSecret();
const token = await sealToken(secret, { userId: 'u_1' }, { ttlMs: 900_000, context: 'password-reset' });
const claims = await openToken<{ userId: string }>(secret, token, { context: 'password-reset' });

openToken throws for a bad key, a tampered token, unreadable JSON or an expired expires_at. The message names the stage it failed at without echoing the plaintext.

Tokens

generateToken('cxt', 'prod') produces cxt_prod_<random><checksum>. The prefix makes a leaked token greppable and the 6-character SHA-256 checksum lets a scanner reject a lookalike without a database round trip. parseToken splits one and isValidTokenFormat re-derives the checksum, which covers the prefix and environment, so a relabelled token fails.

The checksum is an integrity check on a public string, not a secret, so it is compared with ===.

Overlap with @magnaboy/oauth-core

@magnaboy/oauth-core exports its own base64ToBytes, bytesToHex, randomBytes and timingSafeEqual from encoding.ts, under different names. The two are independent today. If oauth-core ever takes a dependency here, that duplication should go.