Validation: the dump validators refuse the nulls their shared types don't allow (a null title or note on creation, a null URL on edit); a playlist title of spaces is refused and titles are stored trimmed; usernames and emails are checked by the same USERNAME_RE and EMAIL_RE in shared/validation.ts on sign-up, on PATCH /api/users/me (which checked other limits) and in the sign-up form. Mentions are read by shared/mention.ts's mentionedUsernames, which skips code and URLs as Markdown shows them: https://mastodon.social/@bob or a `@bob` in code notified bob; the editor's suggestions derive their trigger from the same pattern instead of a copy. Following an unknown user is a 404 rather than a foreign-key 500, and following a private playlist a 404 rather than a 403 that confirmed it exists. `ws` and `emojibase` are reserved category slugs, since the server answers them before the app. GET /api/users/me no longer turns every error into a 500. Dead code goes: adminOnlyMiddleware, verifyEmailTransport, listUsers, deleteUser with disconnectUser and the force_logout message only it sent, PATCH /api/notifications/:id/read (with markOneRead), isSlugAvailable, the unreachable 500 in fetchComment, and the `if (!ctx.state.user)` checks behind authMiddleware. 214 backend and 627 frontend tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
/**
|
|
* Fetches Twemoji glyphs from the jsDelivr CDN, for `lib/site-icons.ts` to
|
|
* render the favicons from.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
/** Pinned, so a new Twemoji release can't change the icons unasked. */
|
|
const TWEMOJI_VERSION = "16.0.1";
|
|
|
|
/**
|
|
* Glyphs by codepoint. It only lives as long as the process, and the `icons`
|
|
* task renders one emoji per process.
|
|
*/
|
|
const glyphCache = new Map<string, string>();
|
|
|
|
/**
|
|
* Returns Twemoji's SVG for `emoji`, whose file name `cp` comes from
|
|
* `emojiToCodepoint`. `emoji` only serves the error message. It fetches with
|
|
* plain `fetch`, since the host is fixed, and gives up after 15 s.
|
|
*
|
|
* @throws {Error} When the glyph can't be fetched.
|
|
*/
|
|
export async function fetchTwemojiSvg(
|
|
emoji: string,
|
|
cp: string,
|
|
): Promise<string> {
|
|
const cached = glyphCache.get(cp);
|
|
if (cached) return cached;
|
|
const url =
|
|
`https://cdn.jsdelivr.net/gh/jdecked/twemoji@${TWEMOJI_VERSION}/assets/svg/${cp}.svg`;
|
|
let res: Response;
|
|
try {
|
|
// The icons task runs before the server starts, so a stalled CDN
|
|
// mustn't hold the start up forever.
|
|
res = await fetch(url, { signal: AbortSignal.timeout(15_000) });
|
|
} catch (cause) {
|
|
throw new Error(
|
|
`Failed to fetch Twemoji glyph for "${emoji}" (${cp}) from ${url}`,
|
|
{ cause },
|
|
);
|
|
}
|
|
if (!res.ok) {
|
|
throw new Error(
|
|
`Failed to fetch Twemoji glyph for "${emoji}" (${cp}) from ${url}: ${res.status} ${res.statusText}`,
|
|
);
|
|
}
|
|
const svg = await res.text();
|
|
glyphCache.set(cp, svg);
|
|
return svg;
|
|
}
|