Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 4ce232ee856d7de1ba5f2b6d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
functions/lib/bio.ts
// What a Bio page may hold, shared by the web app and the public API.
import { SITE_URL } from "./config";
export const MAX_NAME = 60;
export const MAX_BIO = 160;
export const MAX_LINKS = 50;
export const MAX_TITLE = 80;
export const MAX_URL = 2000;
export const MAX_AVATAR = 60000;
export const THEMES = ["paper", "ink", "cobalt", "sage", "night"] as const;
export type Theme = (typeof THEMES)[number];
export const BUTTON_STYLES = ["filled", "outline"] as const;
export type ButtonStyle = (typeof BUTTON_STYLES)[number];
export type SocialKind = "website" | "email" | "x" | "instagram" | "tiktok" | "youtube" | "linkedin" | "github" | "threads" | "bluesky"
| "facebook" | "whatsapp" | "telegram" | "spotify" | "twitch";
export type Socials = Partial<Record<SocialKind, string>>;
/** A link on the page, or a header (a title between links, with an empty url). Hidden ones stay in the list but do not show. */
export interface Link { id: string; title: string; url: string; header?: true; hidden?: true }
/** A page as stored: links and socials are JSON strings. */
export interface PageDoc {
name: string; bio: string; avatar: string; theme: string; buttonStyle: string;
links: string; socials: string; showCredit: boolean; views: number; createdBy: string; createdAt: number;
}
/** A page as shown, edited and returned by the API. */
export interface Page {
name: string; bio: string; avatar: string; theme: Theme; buttonStyle: ButtonStyle;
links: Link[]; socials: Socials; showCredit: boolean;
}
type Checked<T> = T | { error: string };
// Paths the site uses itself. Handles cannot hold a dot, so files like llms.txt are already safe.
// Keep in sync with the inline script at the top of index.html.
export const RESERVED = ["api", "new", "admin", "about", "help", "assets", "art", "fonts", "edit", "login", "signup",
"settings", "privacy", "terms", "support", "app", "apps", "www", "bio", "openapps", "static", "dashboard"];
const HANDLE_RE = /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
const LINK_ID_RE = /^[A-Za-z0-9]{6,16}$/;
const EMAIL_RE = /^[^\s@<>"]+@[^\s@<>"]+\.[^\s@<>"]+$/;
const AVATAR_RE = /^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
const KEY_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
interface SocialDef { kind: SocialKind; label: string; placeholder: string; hosts?: string[]; base?: string; re?: RegExp }
/** The social icons a page can show, in the order they appear. */
export const SOCIALS: SocialDef[] = [
{ kind: "website", label: "Website", placeholder: "yoursite.com" },
{ kind: "email", label: "Email", placeholder: "you@example.com" },
{ kind: "x", label: "X", placeholder: "@yourname", hosts: ["x.com", "twitter.com"], base: "https://x.com/", re: /^[A-Za-z0-9_]{1,15}$/ },
{ kind: "instagram", label: "Instagram", placeholder: "@yourname", hosts: ["instagram.com"], base: "https://www.instagram.com/", re: /^[A-Za-z0-9._]{1,30}$/ },
{ kind: "tiktok", label: "TikTok", placeholder: "@yourname", hosts: ["tiktok.com"], base: "https://www.tiktok.com/@", re: /^[A-Za-z0-9._]{2,24}$/ },
{ kind: "youtube", label: "YouTube", placeholder: "@yourchannel", hosts: ["youtube.com", "youtu.be"], base: "https://www.youtube.com/@", re: /^[A-Za-z0-9._-]{3,30}$/ },
{ kind: "linkedin", label: "LinkedIn", placeholder: "yourname", hosts: ["linkedin.com"], base: "https://www.linkedin.com/in/", re: /^[A-Za-z0-9-]{3,100}$/ },
{ kind: "github", label: "GitHub", placeholder: "yourname", hosts: ["github.com"], base: "https://github.com/", re: /^[A-Za-z0-9-]{1,39}$/ },
{ kind: "threads", label: "Threads", placeholder: "@yourname", hosts: ["threads.com", "threads.net"], base: "https://www.threads.com/@", re: /^[A-Za-z0-9._]{1,30}$/ },
{ kind: "bluesky", label: "Bluesky", placeholder: "you.bsky.social", hosts: ["bsky.app"], base: "https://bsky.app/profile/", re: /^[A-Za-z0-9.-]{3,253}$/ },
{ kind: "facebook", label: "Facebook", placeholder: "yourname", hosts: ["facebook.com", "fb.com"], base: "https://www.facebook.com/", re: /^[A-Za-z0-9.]{5,50}$/ },
{ kind: "whatsapp", label: "WhatsApp", placeholder: "+1 555 123 4567", hosts: ["wa.me", "whatsapp.com"], base: "https://wa.me/", re: /^\d{7,15}$/ },
{ kind: "telegram", label: "Telegram", placeholder: "@yourname", hosts: ["t.me", "telegram.me"], base: "https://t.me/", re: /^[A-Za-z0-9_]{5,32}$/ },
{ kind: "spotify", label: "Spotify", placeholder: "open.spotify.com/artist/...", hosts: ["open.spotify.com", "spotify.com"] },
{ kind: "twitch", label: "Twitch", placeholder: "yourname", hosts: ["twitch.tv"], base: "https://www.twitch.tv/", re: /^[A-Za-z0-9_]{4,25}$/ },
];
function random(alphabet: string, length: number): string {
const out: string[] = [];
const limit = 256 - (256 % alphabet.length);
while (out.length < length) {
for (const b of crypto.getRandomValues(new Uint8Array(length * 2))) {
if (b < limit && out.length < length) out.push(alphabet[b % alphabet.length]);
}
}
return out.join("");
}
export const randomKey = () => random(KEY_ALPHABET, 24);
export const randomLinkId = () => random(KEY_ALPHABET, 8);
export const pageUrl = (handle: string) => `${SITE_URL}/${handle}`;
export const editUrl = (handle: string, key: string) => `${SITE_URL}/e/${handle}#k=${key}`;
export const hostOf = (url: string) => { try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return url; } };
const oneLine = (raw: unknown) => String(raw ?? "").replace(/\s+/g, " ").trim();
export const newPage = (name: string): Page => ({
name, bio: "", avatar: "", theme: "paper", buttonStyle: "filled", links: [], socials: {}, showCredit: true,
});
/** Checks a page address. "@Maya" becomes "maya". */
export function checkHandle(raw: unknown): Checked<{ handle: string }> {
const handle = String(raw ?? "").trim().replace(/^[@/]+|\/+$/g, "").toLowerCase();
if (!handle) return { error: "Pick the name for your page address." };
if (!HANDLE_RE.test(handle)) return { error: "Use 3 to 30 lowercase letters, numbers or dashes, starting and ending with a letter or number." };
if (RESERVED.includes(handle)) return { error: `"${handle}" is reserved. Try another.` };
return { handle };
}
/** Normalizes a web address, or says what is wrong with it. "example.com/x" becomes "https://example.com/x". */
export function checkUrl(raw: unknown): Checked<{ url: string }> {
let text = String(raw ?? "").trim();
if (!text) return { error: "Add the web address." };
const scheme = text.match(/^([a-z][a-z0-9+.-]*):/i);
if (!scheme || (!/^[a-z][a-z0-9+.-]*:\/\//i.test(text) && scheme[1].includes("."))) text = `https://${text}`;
let u: URL;
try {
u = new URL(text);
} catch {
return { error: "That does not look like a web address." };
}
if (u.protocol !== "http:" && u.protocol !== "https:") return { error: "Only web addresses (http or https) work here." };
if (!u.hostname.includes(".")) return { error: "That does not look like a web address." };
if (u.href.length > MAX_URL) return { error: `Web addresses can be up to ${MAX_URL.toLocaleString("en-US")} characters long.` };
return { url: u.href };
}
export function checkName(raw: unknown): Checked<{ name: string }> {
const name = oneLine(raw);
if (!name) return { error: "Add a name for the page." };
if (name.length > MAX_NAME) return { error: `Names can be up to ${MAX_NAME} characters.` };
return { name };
}
export function checkBio(raw: unknown): Checked<{ bio: string }> {
const bio = oneLine(raw);
if (bio.length > MAX_BIO) return { error: `The bio can be up to ${MAX_BIO} characters.` };
return { bio };
}
export const isAvatar = (s: unknown): s is string => typeof s === "string" && s.length <= MAX_AVATAR && AVATAR_RE.test(s);
export function checkAvatar(raw: unknown): Checked<{ avatar: string }> {
if (raw == null || raw === "") return { avatar: "" };
if (!isAvatar(raw)) return { error: `avatar must be a PNG, JPEG or WebP data URL up to ${MAX_AVATAR.toLocaleString("en-US")} characters (a 256 by 256 picture fits).` };
return { avatar: raw };
}
/** Checks the list of links and headers. Ids are kept when sent back, or matched by address, so click counts survive edits. */
export function checkLinks(raw: unknown, current: Link[] = []): Checked<{ links: Link[] }> {
if (raw == null) return { links: [] };
if (!Array.isArray(raw)) return { error: "links must be a list like [{\"title\": \"My shop\", \"url\": \"https://...\"}]." };
if (raw.length > MAX_LINKS) return { error: `A page can have up to ${MAX_LINKS} links and headers.` };
const links: Link[] = [];
const seen = new Set<string>();
for (const [i, item] of raw.entries()) {
const entry = (item && typeof item === "object" ? item : { url: item }) as Record<string, unknown>;
for (const flag of ["header", "hidden"]) {
if (entry[flag] !== undefined && typeof entry[flag] !== "boolean") return { error: `Link ${i + 1}: ${flag} must be true or false.` };
}
const hidden = entry.hidden === true ? { hidden: true as const } : {};
let title = oneLine(entry.title);
let url = "";
if (entry.header !== true) {
const checked = checkUrl(entry.url);
if ("error" in checked) return { error: `Link ${i + 1}: ${checked.error}` };
url = checked.url;
title = title || hostOf(url);
} else if (!title) return { error: `Link ${i + 1}: a header needs a title.` };
if (title.length > MAX_TITLE) return { error: `Link ${i + 1}: titles can be up to ${MAX_TITLE} characters.` };
let id = typeof entry.id === "string" && LINK_ID_RE.test(entry.id) ? entry.id : current.find((l) => url && l.url === url && !seen.has(l.id))?.id;
if (!id || seen.has(id)) id = randomLinkId();
seen.add(id);
links.push(url ? { id, title, url, ...hidden } : { id, title, url, header: true, ...hidden });
}
return { links };
}
/** Checks one social: a full address on that site, or just the name ("@maya" becomes "https://x.com/maya"). */
export function checkSocial(kind: SocialKind, raw: unknown): Checked<{ value: string }> {
const def = SOCIALS.find((s) => s.kind === kind)!;
const text = String(raw ?? "").trim();
if (kind === "website") {
const c = checkUrl(text);
return "error" in c ? c : { value: c.url };
}
if (kind === "email") {
const email = text.replace(/^mailto:/i, "");
return EMAIL_RE.test(email) && email.length <= 254 ? { value: email } : { error: "That does not look like an email address." };
}
const hosts = def.hosts!;
const bare = text.toLowerCase().replace(/^www\.|^m\./, "");
if (/^https?:\/\//i.test(text) || hosts.some((h) => bare.startsWith(`${h}/`))) {
const c = checkUrl(text);
if ("error" in c) return c;
const host = new URL(c.url).hostname.toLowerCase();
if (!hosts.some((h) => host === h || host.endsWith(`.${h}`))) return { error: `Paste your ${def.label} address${def.base ? ", or just the name" : ""}.` };
return { value: c.url.replace(/^http:/i, "https:") };
}
if (!def.base) return { error: `Paste your ${def.label} address.` };
let name = kind === "whatsapp" ? text.replace(/[\s().+-]/g, "") : text.replace(/^@/, "");
if (kind === "bluesky" && !name.includes(".")) name += ".bsky.social";
if (!def.re!.test(name)) return { error: kind === "whatsapp" ? "Type the number with its country code, like +1 555 123 4567." : `That ${def.label} name does not look right.` };
return { value: def.base + name };
}
export function checkSocials(raw: unknown): Checked<{ socials: Socials }> {
if (raw == null) return { socials: {} };
if (typeof raw !== "object" || Array.isArray(raw)) return { error: "socials must be an object like {\"instagram\": \"@maya\"}." };
const socials: Socials = {};
for (const [kind, value] of Object.entries(raw)) {
const def = SOCIALS.find((s) => s.kind === kind);
if (!def) return { error: `Unknown social "${kind}". Use ${SOCIALS.map((s) => s.kind).join(", ")}.` };
if (value == null || String(value).trim() === "") continue;
const c = checkSocial(def.kind, value);
if ("error" in c) return { error: `${def.label}: ${c.error}` };
socials[def.kind] = c.value;
}
return { socials };
}
/** Applies the fields present in input (as the API receives them) on top of base. */
export function applyInput(input: Record<string, unknown>, base: Page): Checked<{ page: Page }> {
const page = { ...base };
if (input.name !== undefined) {
const c = checkName(input.name);
if ("error" in c) return c;
page.name = c.name;
}
if (input.bio !== undefined) {
const c = checkBio(input.bio);
if ("error" in c) return c;
page.bio = c.bio;
}
if (input.avatar !== undefined) {
const c = checkAvatar(input.avatar);
if ("error" in c) return c;
page.avatar = c.avatar;
}
if (input.theme !== undefined) {
if (!THEMES.includes(input.theme as Theme)) return { error: `theme must be one of ${THEMES.join(", ")}.` };
page.theme = input.theme as Theme;
}
if (input.buttonStyle !== undefined) {
if (!BUTTON_STYLES.includes(input.buttonStyle as ButtonStyle)) return { error: `buttonStyle must be ${BUTTON_STYLES.join(" or ")}.` };
page.buttonStyle = input.buttonStyle as ButtonStyle;
}
if (input.links !== undefined) {
const c = checkLinks(input.links, base.links);
if ("error" in c) return c;
page.links = c.links;
}
if (input.socials !== undefined) {
const c = checkSocials(input.socials);
if ("error" in c) return c;
page.socials = c.socials;
}
if (input.showCredit !== undefined) {
if (typeof input.showCredit !== "boolean") return { error: "showCredit must be true or false." };
page.showCredit = input.showCredit;
}
return { page };
}
/** The stored fields for a page (everything an owner may change). */
export const toDoc = (p: Page) => ({
name: p.name, bio: p.bio, avatar: p.avatar, theme: p.theme, buttonStyle: p.buttonStyle,
links: JSON.stringify(p.links), socials: JSON.stringify(p.socials), showCredit: p.showCredit,
});
function parseJson(text: unknown): unknown {
try { return JSON.parse(String(text)); } catch { return null; }
}
/** Reads a stored page defensively: anything malformed is dropped rather than shown. */
export function fromDoc(doc: PageDoc): Page {
const rawLinks = parseJson(doc.links);
const links: Link[] = [];
for (const l of Array.isArray(rawLinks) ? rawLinks : []) {
if (!l || typeof l.id !== "string" || typeof l.title !== "string") continue;
const hidden = l.hidden === true ? { hidden: true as const } : {};
if (l.header === true) links.push({ id: l.id, title: l.title, url: "", header: true, ...hidden });
else if (typeof l.url === "string" && /^https?:\/\//i.test(l.url)) links.push({ id: l.id, title: l.title, url: l.url, ...hidden });
}
const rawSocials = parseJson(doc.socials);
const socials: Socials = {};
if (rawSocials && typeof rawSocials === "object") {
for (const { kind } of SOCIALS) {
const v = (rawSocials as Record<string, unknown>)[kind];
if (typeof v === "string" && (kind === "email" ? EMAIL_RE.test(v) : /^https?:\/\//i.test(v))) socials[kind] = v;
}
}
return {
name: String(doc.name ?? ""),
bio: String(doc.bio ?? ""),
avatar: isAvatar(doc.avatar) ? doc.avatar : "",
theme: THEMES.includes(doc.theme as Theme) ? (doc.theme as Theme) : "paper",
buttonStyle: BUTTON_STYLES.includes(doc.buttonStyle as ButtonStyle) ? (doc.buttonStyle as ButtonStyle) : "filled",
links,
socials,
showCredit: doc.showCredit !== false,
};
}
Change history
Quality pass: reorder, hide links, headers, QR share, 15 social icons, API docs
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync