@broberg/gravatar
v0.2.1
Published
Isomorphic Gravatar helper (SHA-256) + initials fallback. Headless core + React and Preact adapter components with 3-tier fallback: uploaded picture → Gravatar → generated initials.
Maintainers
Readme
@broberg/gravatar
Gravatar URLs and avatar initials for the fleet. Zero runtime dependencies, isomorphic.
npm i @broberg/gravatarimport { gravatarUrl, gravatarLookup, getInitials } from "@broberg/gravatar";
await gravatarUrl("[email protected]", { size: 80, default: "404" });
await gravatarLookup("[email protected]"); // 'yes' | 'no' | 'unknown'
getInitials("Christian Broberg"); // 'CB'Also ships @broberg/gravatar/react and @broberg/gravatar/preact.
gravatarLookup() — three outcomes, because there are three
const presence = await gravatarLookup("[email protected]");
if (presence !== "unknown") cache.set(email, presence); // never cache a guess| Response | Result | Meaning |
|---|---|---|
| 404 | no | genuinely no avatar |
| 200 | yes | |
| 5xx · a throw · a timeout · a surprise status | unknown | do not cache this as a no |
Why it exists (v0.2.0). The old boolean collapsed three facts into two: a 503 from Automattic and a dropped connection both came back false, which reads as "there is no picture".
And you cache that answer — you have to, or you ask Automattic on every page render. So a ten-second hiccup cost a user their avatar until the cache expired, with nothing at the call-site able to tell. Found by a consumer on the day they adopted this package.
gravatarExists() is unchanged for existing callers and still cannot tell those two apart — both are false. If you store its result, you are storing a guess on every failure. Use gravatarLookup for anything you cache.
getInitials() — letters, not characters
getInitials("Lens (verifikation)"); // 'LV' (was 'L(' before v0.2.0)
getInitials(null, "[email protected]"); // 'X' (was 'X@')
getInitials(" "); // '??' (was two spaces)
getInitials("李 明"); // '李明'
getInitials("José"); // 'JO'Splits on non-letter/non-digit, unicode-aware — so CJK and accented Latin work, which an ASCII-only test suite would never have caught.
The email branch takes the prefix before @. That is what its documentation always claimed; the code read the whole address and only looked right because most addresses happen to begin with two letters.
An email in the name slot works (v0.2.1). getInitials("[email protected]") → CB, identical to getInitials(null, "[email protected]") — because navn = bruger.navn || bruger.email is an ordinary call-site and the two arguments must agree. Detection is conservative: exactly one @, non-empty and whitespace-free on both sides, so "Anne @ Hansen" stays a name.
0.2.0 got this wrong and shipped it: every address in the name slot came out ending in the TLD's first letter (
[email protected]→CD). The acceptance criterion that should have caught it asserted the same string in the email argument only — the claim was about the common case, the test was about one argument position, and both were green.
Known limitation, deliberate. getInitials("Jens Hansen, direktør") returns JD — it takes the title as the surname. Truncating at the first comma would fix the Danish "Name, Title" form and break the equally common "Surname, Firstname" export. Two conventions, opposite fixes, and nothing in the string to tell them apart. Left alone rather than guessed at.
The core is async, on purpose
gravatarHash and gravatarUrl return promises because they use crypto.subtle — the one hashing API that exists in Node, Bun, Deno, workers and the browser.
A Node-only consumer expects a synchronous createHash and will be surprised. That is the isomorphism tax, and it is deliberate: the alternative is two code paths and a bundler condition on every consumer.
API
| Export | |
|---|---|
| gravatarHash(email) | SHA-256 of the trimmed, lowercased address |
| gravatarUrl(email, { size?, default? }) | the full image URL |
| gravatarLookup(email, size?) | 'yes' \| 'no' \| 'unknown' |
| gravatarExists(email, size?) | boolean — lossy; see above |
| getInitials(name?, email?) | up to two uppercase characters, or '??' |
