bio-free.openapps.xyz

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.
Owned app · rev 3e98d5e10bac
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.
← All files
functions/api.ts
// Bio's public HTTP API: no account. Anyone can make a page; the owner key that comes back is what
// lets you change it and read its stats later. Every route answers JSON; GET / describes the routes.
// Writes go through the same policy rules as the web app.
import { SITE_URL } from "./lib/config";
import { applyInput, checkHandle, fromDoc, newPage, pageUrl, editUrl, randomKey, toDoc, type Page, type PageDoc } from "./lib/bio";
import { qrMatrix, qrSvg } from "./lib/qr";

const NOW = { operation: "time", value: "now" };
const PLUS_ONE = { operation: "increment", value: 1 };
const HANDLE_RE = /^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/;
const FIELDS = ["name", "bio", "avatar", "links", "socials", "theme", "buttonStyle", "showCredit"];

const cors = {
  "access-control-allow-origin": "*",
  "access-control-allow-methods": "GET, POST, PATCH, OPTIONS",
  "access-control-allow-headers": "content-type, x-owner-key",
};
const json = (body: unknown, status = 200) =>
  new Response(JSON.stringify(body, null, 2), { status, headers: { "content-type": "application/json; charset=utf-8", ...cors } });
const fail = (status: number, error: string) => json({ error }, status);

class HttpError extends Error { constructor(public status: number, message: string) { super(message); } }

async function body(req: Request): Promise<Record<string, unknown>> {
  try {
    const parsed = await req.json();
    if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
  } catch { /* fall through */ }
  throw new HttpError(400, "Send a JSON object body.");
}

async function load(ctx: any, handle: string): Promise<PageDoc> {
  if (!HANDLE_RE.test(handle)) throw new HttpError(404, "There is no page at this address.");
  let doc = await ctx.bounded.get(`pages/${handle}`);
  // Now and then a read comes back with none of the page's fields. The rules give every page a name,
  // so read once more rather than draw a blank page or let a PATCH wipe the links.
  if (doc && !doc.name) doc = await ctx.bounded.get(`pages/${handle}`);
  if (!doc) throw new HttpError(404, "There is no page at this address.");
  if (!doc.name) throw new HttpError(503, "The page did not load. Try again in a moment.");
  return doc;
}

/** Every API caller shares one identity, so owner actions always check the key first. */
async function ownerKey(ctx: any, req: Request, handle: string): Promise<string> {
  const key = req.headers.get("x-owner-key")?.trim() || "";
  if (!key) throw new HttpError(401, "Send the page's owner key in the x-owner-key header.");
  const stored = await ctx.bounded.get(`pagekeys/${handle}`);
  if (!stored || stored.key !== key) throw new HttpError(403, "That owner key does not match this page.");
  return key;
}

async function listAll(ctx: any, path: string): Promise<any[]> {
  const rows: any[] = [];
  let cursor: string | undefined;
  for (let page = 0; page < 20; page++) {
    const res = await ctx.bounded.get(path, { limit: 500, ...(cursor ? { cursor } : {}) });
    for (const r of res?.data || []) rows.push({ ...r, id: r.id ?? String(r._id ?? r.pathId ?? "").split("/").pop() });
    cursor = res?.nextCursor;
    if (!cursor) break;
  }
  return rows;
}

function describe(handle: string, page: Page, doc: { views?: number; createdAt?: number }) {
  return {
    handle,
    url: pageUrl(handle),
    ...page,
    views: doc.views ?? 0,
    createdAt: new Date((doc.createdAt ?? Math.floor(Date.now() / 1000)) * 1000).toISOString(),
  };
}

const routes = {
  name: "Bio by OpenApps",
  about: "Free link in bio pages: a photo, a name, a short bio, your links and social icons at one address. No account and no key to make a page; the owner key you get back lets you change it and see its views and clicks.",
  docs: `${SITE_URL}/llms.txt`,
  openapi: `${SITE_URL}/openapi.json`,
  routes: [
    "POST /pages {handle, name, bio?, avatar?, links?: [{title, url, hidden?} or {title, header: true}], socials?, theme?, buttonStyle?, showCredit?} -> the page, its private edit link and the owner key",
    "GET /pages/:handle -> the public page as JSON",
    "PATCH /pages/:handle with header x-owner-key {any of the fields above} -> the updated page",
    "GET /pages/:handle/stats with header x-owner-key -> views and clicks per link",
    "GET /pages/:handle/qr.svg -> a QR code of the page address, as an SVG image",
    "GET /view/:handle -> the public page as JSON, counting one view (what the page itself does)",
    "GET /go/:handle/:linkId -> {url}: where a link goes (counts one click)",
  ],
};

export default async function api(req: Request, ctx: any) {
  if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors });
  const url = new URL(req.url);
  const parts = url.pathname.split("/").filter(Boolean);
  // Accept both /pages/... and /api/pages/... so a copied path works either way.
  if (parts[0] === "api") parts.shift();
  try {
    if (!parts.length && req.method === "GET") return json(routes);
    const me = ctx.user.id;

    if (parts[0] === "go" && parts.length === 3 && req.method === "GET") {
      const page = fromDoc(await load(ctx, parts[1]));
      const link = page.links.find((l) => l.id === parts[2] && !l.header && !l.hidden);
      if (!link) return fail(404, "This link is no longer on the page.");
      try { await ctx.bounded.set(`pages/${parts[1]}/clicks/${link.id}`, { count: PLUS_ONE }); } catch { /* a missed count never blocks the visit */ }
      return json({ url: link.url });
    }

    if (parts[0] === "view" && parts.length === 2 && req.method === "GET") {
      // Count while reading, so the visitor never waits for one after the other. The rules refuse the
      // count when there is no page, so a wrong address creates nothing. A missed count never blocks the visit.
      const counted = HANDLE_RE.test(parts[1]) ? ctx.bounded.set(`pages/${parts[1]}`, { views: PLUS_ONE }).catch(() => {}) : null;
      const [doc] = await Promise.all([load(ctx, parts[1]), counted]);
      return json(describe(parts[1], fromDoc(doc), doc));
    }

    if (parts[0] !== "pages") return fail(404, "Unknown route. GET / lists the routes.");

    if (parts.length === 1 && req.method === "POST") {
      const input = await body(req);
      const h = checkHandle(input.handle);
      if ("error" in h) return fail(400, h.error);
      if (input.name === undefined) return fail(400, "Add a name for the page.");
      const checked = applyInput(input, newPage(""));
      if ("error" in checked) return fail(400, checked.error);
      if (await ctx.bounded.get(`pages/${h.handle}`)) return fail(409, `${pageUrl(h.handle).replace(/^https?:\/\//, "")} is taken. Try another handle.`);
      const key = randomKey();
      try {
        await ctx.bounded.setMany([
          { path: `pages/${h.handle}`, document: { ...toDoc(checked.page), views: 0, createdBy: me, createdAt: NOW } },
          { path: `pages/${h.handle}/owners/${me}`, document: { at: NOW } },
          { path: `pagekeys/${h.handle}`, document: { key } },
        ]);
      } catch {
        if (await ctx.bounded.get(`pages/${h.handle}`)) return fail(409, `${pageUrl(h.handle).replace(/^https?:\/\//, "")} is taken. Try another handle.`);
        throw new Error("The page could not be saved. Try again.");
      }
      return json({ ...describe(h.handle, checked.page, { views: 0 }), editUrl: editUrl(h.handle, key), ownerKey: key }, 201);
    }

    const handle = parts[1];
    if (!handle || parts.length > 3) return fail(404, "Unknown route. GET / lists the routes.");

    if (parts.length === 3 && parts[2] === "qr.svg" && req.method === "GET") {
      await load(ctx, handle);
      return new Response(qrSvg(qrMatrix(pageUrl(handle))), {
        headers: { "content-type": "image/svg+xml; charset=utf-8", "cache-control": "public, max-age=86400", ...cors },
      });
    }

    if (parts.length === 3) {
      if (parts[2] !== "stats" || req.method !== "GET") return fail(404, "Unknown route. GET / lists the routes.");
      const doc = await load(ctx, handle);
      await ownerKey(ctx, req, handle);
      const counts = new Map((await listAll(ctx, `pages/${handle}/clicks`)).map((r) => [r.id, Number(r.count) || 0]));
      const links = fromDoc(doc).links.filter((l) => !l.header).map((l) => ({ ...l, clicks: counts.get(l.id) ?? 0 }));
      return json({ handle, url: pageUrl(handle), views: doc.views ?? 0, clicks: links.reduce((sum, l) => sum + l.clicks, 0), links });
    }

    if (req.method === "GET") {
      const doc = await load(ctx, handle);
      return json(describe(handle, fromDoc(doc), doc));
    }

    if (req.method === "PATCH") {
      const doc = await load(ctx, handle);
      const key = await ownerKey(ctx, req, handle);
      const input = await body(req);
      if (!FIELDS.some((f) => input[f] !== undefined)) return fail(400, `Send at least one of ${FIELDS.join(", ")}.`);
      const checked = applyInput(input, fromDoc(doc));
      if ("error" in checked) return fail(400, checked.error);
      // Become an owner with the key (once), then change the page as one.
      if (!(await ctx.bounded.get(`pages/${handle}/owners/${me}`))) {
        await ctx.bounded.setMany([
          { path: `pageclaims/${handle}/c/${me}`, document: { key } },
          { path: `pages/${handle}/owners/${me}`, document: { at: NOW } },
        ]);
      }
      await ctx.bounded.set(`pages/${handle}`, toDoc(checked.page));
      return json(describe(handle, checked.page, doc));
    }

    return fail(404, "Unknown route. GET / lists the routes.");
  } catch (err) {
    if (err instanceof HttpError) return fail(err.status, err.message);
    const message = err instanceof Error ? err.message : String(err);
    return fail(500, `Something went wrong: ${message}`);
  }
}

Change history

Quality pass: reorder, hide links, headers, QR share, 15 social icons, API docs
3e98d5e10b · 4 hours ago
Bounded source sync
c1194348e3 · 1 day ago
Bounded source sync
5b1f3fab35 · 1 day ago
Bounded source sync
21e45889ea · 1 day ago
Bounded source sync
8e21b0e5b7 · 1 day ago
Bounded source sync
0598bcc9b2 · 1 day ago
Bounded source sync
0ea5668865 · 1 day ago